从Oracle中删除指定行的最佳方法

时间:2012-04-10 16:12:10

标签: oracle

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

我的问题是,我似乎无法找到实际执行删除的有效方法。出于测试的目的,我在单个测试表上运行7384个删除行,该测试表有大约9400行。我在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声明,其中有7384个OR条款 127.4s

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

1 DELETE from (SELECT)声明,其中有7384 OR条款 74.4s

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

虽然最后一个可能是最快的,但是在进一步测试时它仍然非常慢,当从9000行表扩展到甚至只有200,000行表(仍然是最终表集大小的1%)时声明需要 14分钟才能运行。而<每行快50%,在针对完整数据集运行时仍可推断大约一天。我有充分的权威,我们用来执行此任务的软件可以在 20mins 中完成。

所以我的问题是:

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

4 个答案:

答案 0 :(得分:15)

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

以相对的方式尽量减少陈述的数量及其发布的工作。

所有方案都假设您有一个要从PURGE_IDSTABLE_1等删除的ID(TABLE_2)表。

考虑将CREATE TABLE AS SELECT用于实际的大型删除

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

不要运行您知道不会删除任何行的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循环,发出rowcount-limited delete语句。如果对正在运行删除的表进行重要的插入/更新/删除并发加载,这很可能是合适的。

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;

答案 1 :(得分:1)

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

2)使用Oracle批量删除

3)使用相关删除查询。

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

答案 2 :(得分:0)

首先,在删除过程中禁用索引会很有帮助。

尝试使用MERGE INTO语句:
1)使用TABLE和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

答案 3 :(得分: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
);