15

我有一个项目需要偶尔从六个不同大小的表之一中删除数万行,但它们之间有大约 3000 万行。由于我获得的数据结构,我不知道六个表中的哪个表中有需要删除的行,因此我必须对所有表运行所有删除操作。我已经针对 ID 列构建了一个 INDEX 来尝试加快速度,但如果这样可以加快速度,可以将其删除。

我的问题是,我似乎无法找到一种有效的方法来实际执行删除。出于测试的目的,我正在针对大约 9400 行的单个测试表运行 7384 个删除行。我在 Oracle SQL Developer 中测试了许多可能的查询解决方案:

7384 个单独的DELETE语句耗时203秒:

delete from TABLE1 where ID=1000001356443294;
delete from TABLE1 where ID=1000001356443296;
etc...

7384 个单独的SELECT语句耗时57秒:

select ID from TABLE1 where ID=1000001356443294
select ID from TABLE1 where ID=1000001356443296
etc...

7384 条单独的DELETE from (SELECT)语句耗时214秒:

delete from (select ID from TABLE1 where ID=1000001356443294);
delete from (select ID from TABLE1 where ID=1000001356443296);
etc...

1条SELECT语句在 where 中包含 7384个OR子句127.4s

select ID from TABLE1 where ID=1000001356443294 or ID = 1000001356443296 or ...

1条DELETE from (SELECT)语句在 where 中包含 7384个OR子句,时间为74.4s

delete from (select ID from TABLE1 where ID=1000001356443294 or ID = 1000001356443296 or ...)

虽然最后一个可能是最快的,但经过进一步测试,当从 9000 行表扩展到甚至只有 200,000 行表(仍然小于最终表集大小的 1%)时,它仍然非常慢,其中相同的语句需要14 分钟才能完成跑。虽然每行速度快了 50% 以上,但在针对完整数据集运行时,这仍然可以推断出大约一天。我有很好的权威,我们用来完成这项任务的软件可以在大约20 分钟内完成。

所以我的问题是:

  • 有没有更好的删除方法?
  • 我是否应该使用一轮SELECT语句(即,像第二个测试)来发现任何给定行在哪个表中,然后执行删除查询?即使这样看起来很慢,但是......
  • 我还能做些什么来加快删除速度吗?我没有 DBA 级别的访问权限或知识。
4

4 回答 4

15

在回答我的问题之前,我会这样做:

尽量减少相关声明的数量和它们所做的工作。

所有方案都假设您有一个 ID 表 ( PURGE_IDS) 可从中删除TABLE_1TABLE_2等。

考虑使用 CREATE TABLE AS SELECT 进行非常大的删除

如果没有并发活动,并且您要删除一个或多个表中 30+ % 的行,请不要删除;对您希望保留的行执行 a create table as select,并将新表换成旧表。 INSERT /*+ APPEND */ ... NOLOGGING如果你能负担得起,它会非常便宜。即使您确实有一些并发活动,您也可以使用在线表重新定义来就地重建表。

不要运行你知道不会删除任何行的 DELETE 语句

如果一个 ID 值最多存在于六个表中的一个中,则跟踪您删除了哪些 ID - 不要尝试从任何其他表中删除这些 ID。

CREATE TABLE TABLE1_PURGE NOLOGGING
AS 
SELECT ID FROM PURGE_IDS INNER JOIN TABLE_1 ON PURGE_IDS.ID = TABLE_1.ID;

DELETE FROM TABLE1 WHERE ID IN (SELECT ID FROM TABLE1_PURGE);

DELETE FROM PURGE_IDS WHERE ID IN (SELECT ID FROM TABLE1_PURGE);

DROP TABLE TABLE1_PURGE;

并重复。

必要时管理并发

另一种方法是在表上使用 PL/SQL 循环,发出行数限制的删除语句。如果针对您正在对其运行删除的表有大量的插入/更新/删除并发负载,这很可能是合适的。

declare
  l_sql varchar2(4000);
begin
  for i in (select table_name from all_tables 
             where table_name in ('TABLE_1', 'TABLE_2', ...)
             order by table_name);
  loop
    l_sql := 'delete from ' || i.table_name || 
             ' where id in (select id from purge_ids) ' || 
             '   and rownum <= 1000000';
    loop
      commit;
      execute immediate l_sql;
      exit when sql%rowcount <> 1000000;  -- if we delete less than 1,000,000
    end loop;                             -- no more rows need to be deleted!
  end loop;
  commit;
end;
于 2012-04-10T18:52:28.853 回答
1

将所有要删除的 ID 存储到一个表中。然后有3种方式。1) 遍历表中的所有 ID,然后在 X 提交间隔内一次删除一行。X 可以是 100 或 1000。它适用于 OLTP 环境,您可以控制锁。

2)使用Oracle批量删除

3)使用关联删除查询。

单个查询通常比多个查询更快,因为上下文切换更少,并且可能解析更少。

于 2012-04-10T16:28:57.923 回答
0

首先,在删除期间禁用索引会很有帮助。

尝试使用 MERGE INTO 语句:
1)创建一个带有 ID 的临时表和 TABLE1 中的附加列,并使用以下内容进行测试

MERGE INTO table1 src
USING (SELECT id,col1
         FROM test_merge_delete) tgt
ON (src.id = tgt.id)
WHEN MATCHED THEN
  UPDATE
     SET src.col1 = tgt.col1
  DELETE
   WHERE src.id = tgt.id
于 2012-04-10T17:57:49.147 回答
0

我已经尝试过这段代码,在我的情况下它工作正常。

DELETE FROM NG_USR_0_CLIENT_GRID_NEW WHERE rowid IN
( SELECT rowid FROM
  (
      SELECT wi_name, relationship, ROW_NUMBER() OVER (ORDER BY rowid DESC) RN
      FROM NG_USR_0_CLIENT_GRID_NEW
      WHERE wi_name = 'NB-0000001385-Process'
  )
  WHERE RN=2
);
于 2017-03-22T18:25:49.320 回答