在Oracle中删除大型记录集的最佳方法


18

我管理着一个应用程序,该应用程序具有非常大的Oracle数据库后端(一个表中的数据接近1TB,行数超过5亿)。数据库实际上并没有做任何事情(没有SProcs,没有触发器或任何东西),它只是一个数据存储。

每个月我们都需要从两个主表中清除记录。清除的标准各不相同,并且是行龄和几个状态字段的组合。通常,我们最终每月清除10到5000万行(我们每周通过导入增加大约3-5百万行)。

当前,我们必须批量删除约50,000行(即删除50000,提交,删除50000,提交,重复)。尝试一次全部删除整个批处理会使数据库在大约一个小时内无响应(取决于行数)。像这样批量删除行在系统上非常困难,我们通常必须在一周的时间内“在时间允许的情况下”进行删除。允许脚本连续运行可能会导致用户无法接受的性能下降。

我相信这种批量删除还会降低索引性能,并产生其他影响,最终导致数据库性能下降。一张表上有34个索引,索引数据的大小实际上大于数据本身。

这是我们的一位IT人员用来清除此错误的脚本:

BEGIN
LOOP

delete FROM tbl_raw 
  where dist_event_date < to_date('[date]','mm/dd/yyyy') and rownum < 50000;

  exit when SQL%rowcount < 49999;

  commit;

END LOOP;

commit;

END;

该数据库必须达到99.99999%,并且我们每年只有一次2天维护窗口。

我正在寻找一种删除这些记录的更好的方法,但是我还没有找到任何记录。有什么建议么?


还要注意这里有30多个索引在起作用
jcolebrand

Answers:


17

带有“ A”和“ B”的逻辑可能被“隐藏”在可以进行分区的虚拟列后面:

alter session set nls_date_format = 'yyyy-mm-dd';
drop   table tq84_partitioned_table;

create table tq84_partitioned_table (
  status varchar2(1)          not null check (status in ('A', 'B')),
  date_a          date        not null,
  date_b          date        not null,
  date_too_old    date as
                       (  case status
                                 when 'A' then add_months(date_a, -7*12)
                                 when 'B' then            date_b
                                 end
                        ) virtual,
  data            varchar2(100) 
)
partition   by range  (date_too_old) 
( 
  partition p_before_2000_10 values less than (date '2000-10-01'),
  partition p_before_2000_11 values less than (date '2000-11-01'),
  partition p_before_2000_12 values less than (date '2000-12-01'),
  --
  partition p_before_2001_01 values less than (date '2001-01-01'),
  partition p_before_2001_02 values less than (date '2001-02-01'),
  partition p_before_2001_03 values less than (date '2001-03-01'),
  partition p_before_2001_04 values less than (date '2001-04-01'),
  partition p_before_2001_05 values less than (date '2001-05-01'),
  partition p_before_2001_06 values less than (date '2001-06-01'),
  -- and so on and so forth..
  partition p_ values less than (maxvalue)
);

insert into tq84_partitioned_table (status, date_a, date_b, data) values 
('B', date '2008-04-14', date '2000-05-17', 
 'B and 2000-05-17 is older than 10 yrs, must be deleted');


insert into tq84_partitioned_table (status, date_a, date_b, data) values 
('B', date '1999-09-19', date '2004-02-12', 
 'B and 2004-02-12 is younger than 10 yrs, must be kept');


insert into tq84_partitioned_table (status, date_a, date_b, data) values 
('A', date '2000-06-16', date '2010-01-01', 
 'A and 2000-06-16 is older than 3 yrs, must be deleted');


insert into tq84_partitioned_table (status, date_a, date_b, data) values 
('A', date '2009-06-09', date '1999-08-28', 
 'A and 2009-06-09 is younger than 3 yrs, must be kept');

select * from tq84_partitioned_table order by date_too_old;

-- drop partitions older than 10 or 3 years, respectively:

alter table tq84_partitioned_table drop partition p_before_2000_10;
alter table tq84_partitioned_table drop partition p_before_2000_11;
alter table tq84_partitioned_table drop partition p2000_12;

select * from tq84_partitioned_table order by date_too_old;

我可能已经简化了如何确定要清除的记录的逻辑,但这是一个非常有趣的想法。但是,必须考虑的一件事是日常性能。清除是“我们的问题”,客户端将不会接受性能下降的问题来解决。从一些评论和Gary的回答看来,这可能是分区问题?
编码大猩猩

我不确定这是否是我们要寻找的答案,但这绝对是我们将要研究的非常有趣的方法。
编码大猩猩

14

经典的解决方案是按月或周对表进行分区。如果您以前没有遇到过分区表,那么分区表就像是几个结构相同的表,UNION在选择时会带有一个隐式表,并且Oracle在基于分区标准插入行时会自动将行存储在适当的分区中。您提到了索引-每个分区也都有自己的分区索引。在Oracle中删除分区是很便宜的操作(类似于TRUNCATE在负载方面,因为这是您真正要做的-截断或删除这些不可见的子表之一)。“事后”进行分区将是大量的处理过程,但是对溢出的牛奶哭泣是没有道理的-这样做的好处远远超过了成本。每个月,您都会拆分顶部分区,以为下个月的数据创建一个新分区(您可以使用轻松地实现此自动化DBMS_JOB)。

借助分区,您还可以利用并行查询分区消除功能,这应该使您的用户非常满意……


FWIW我们在30Tb +数据库的站点上使用了此技术
Gaius

分区的问题是没有明确的方法来分区数据。在两个表之一(以下未显示)中,用于清除的条件基于两个不同(且不同)的日期字段和一个状态字段。例如,如果状态是A那么如果DateA是3岁以上,它就会被清除。如果“状态”为10岁BDateB已超过10年,则将其清除。如果我对分区的理解是正确的,那么在这种情况下(至少就清除而言)分区将无用。
编码大猩猩

您可以按状态进行分区,按日期范围进行子分区。但是,如果状态(或日期)发生变化,则实际上会从一个子分区中删除并在另一个子分区中插入。简而言之,您可以在日常流程中获得成功,以节省清除时间。
加里

6
或者,您可以创建一个虚拟列,当状态为A时显示DateA,当状态为B时显示DateB,然后在虚拟列上进行分区。将会发生相同的分区迁移,但这将有助于清除。看来这已作为答案发布。
Leigh Riffel

4

要考虑的一方面是索引删除性能的多少以及原始表删除的性能的多少。从表中删除的每个记录都需要从每个btree索引中删除该行。如果您拥有30多个btree索引,我怀疑您的大部分时间都花在了索引维护上。

这会影响分区的有效性。假设您有一个名字索引。标准的Btree索引全部位于一个段中,可能必须执行四次跳转才能从根块跳转到叶块,并进行第五次读取以获取行。如果该索引分为50个段,而您没有分区键作为查询的一部分,则需要检查这50个段中的每个段。每个段都较小,因此您可能只需要执行2次跳转,但最终仍可能执行100次读取,而不是前5次。

如果它们是位图索引,则方程式是不同的。您可能没有使用索引来标识单个行,而是使用它们的集合。因此,它不是使用5个IO返回单个记录的查询,而是使用10,000个IO。因此,索引的额外分区中的额外开销将无关紧要。


2

每月删除5,000万条记录(每50,000个批次)仅1000次迭代。如果您每30分钟执行1次删除操作,则它应满足您的要求。运行您发布的查询的计划任务,但是删除了循环,因此它仅执行一次,不会对用户造成明显的贬损。我们在制造工厂中执行大约24/7的相同数量的记录,并且可以满足我们的需求。实际上,我们每10分钟就会散布10,000条记录,这些记录将在Oracle unix服务器上运行大约1或2秒。


那么将产生大量的“撤消”和“重做”“删除”呢?它也使IO感到窒息...基于'delete'的方法当然应该是NO..NO对于大型表。
pahariayogi

1

如果磁盘空间不是很宝贵,那么您可以my_table_new使用CTAS(选择时创建表)创建标准的表的“工作”副本,并使用标准来删除要删除的记录。您可以并行执行create语句,并使用追加提示使其快速执行,然后构建所有索引。然后,完成(并测试)后,将现有表my_table_old重命名为,并将“工作”表重命名为my_table。一旦您适应了一切,drop my_table_old purge就可以摆脱旧表。如果有一堆外键约束,请查看dbms_redefinition PL / SQL软件包。使用适当的选项时,它将克隆您的索引,约束等。这是AskTom的Tom Kyte的建议的总结名望。第一次运行之后,您可以使所有操作自动化,并且创建表应该更快得多,并且可以在系统启动时完成,并且应用程序的停机时间可以限制在不到一分钟的时间内即可进行表的重命名。使用CTAS的速度将比执行几个批处理删除的速度快得多。如果您没有分区许可,则此方法特别有用。

采样CTAS,并保留最近365天的数据行和flag_inactive = 'N'

create /*+ append */ table my_table_new 
   tablespace data as
   select /*+ parallel */ * from my_table 
       where some_date >= sysdate -365 
       and flag_inactive = 'N';

-- test out my_table_new. then if all is well:

alter table my_table rename to my_table_old;
alter table my_table_new rename to my_table;
-- test some more
drop table my_table_old purge;

1
如果(a)清除是一次性任务,则可以考虑这一点。(b)如果要保留的行数较少,而要删除的大部分数据...
pahariayogi

0

删除分区时,会使全局索引不可用,需要重建,重建全局索引将是一个大问题,就像您在线上进行一样,它将非常缓慢,否则需要停机。无论哪种情况,都不能满足要求。

“我们通常最终每月清除10到5000万行”

我建议使用PL / SQL批处理删除,几个小时就可以了。


1
如果您有主键,则删除分区不应使任何全局索引不可用。但是,如果OP具有很多全局索引,则删除分区的成本将会很高。在理想情况下,当某人正在对表进行分区时,分区是基于主键的,因此他们不需要任何全局索引。每个查询都能够利用分区修剪的优势。
Gandolf989 2014年

@ Gandolf989删除分区将始终使全局索引无法使用
miracle173 '16
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.