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

Cod*_*lla 19 oracle delete oracle-11g

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

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

目前我们必须分批进行大约 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;
Run Code Online (Sandbox Code Playgroud)

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

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

Ren*_*ger 18

'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;
Run Code Online (Sandbox Code Playgroud)


Gai*_*ius 14

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

使用分区,您还可以利用并行查询分区消除,这应该会让您的用户非常高兴......

  • 或者,您可以创建一个虚拟列,当状态为 A 时显示 DateA,当状态为 B 时显示 DateB,然后在虚拟列上进行分区。会发生相同的分区迁移,但它会帮助您清除。看起来这已经作为答案发布了。 (6认同)