删除Oracle中的大量数据

Sar*_*Mei 14 sql oracle plsql

我不是一个数据库人,我的大部分数据库工作都是MySQL,所以请原谅我,如果这个问题中的某些内容非常天真.

我需要从一个有大约1亿行的Oracle表中删除550万行.我有一个临时表中需要删除的行的ID.如果它只是几千行,我会这样做:

DELETE FROM table_name WHERE id IN (SELECT id FROM temp_table);
COMMIT;
Run Code Online (Sandbox Code Playgroud)

有什么我需要注意和/或做的不同,因为它是550万行?我想做一个循环,这样的事情:

DECLARE
  vCT NUMBER(38) := 0;

BEGIN
  FOR t IN (SELECT id FROM temp_table) LOOP
    DELETE FROM table_name WHERE id = t.id;
    vCT := vCT + 1;
    IF MOD(vCT,200000) = 0 THEN
      COMMIT;
    END IF;
  END LOOP;
  COMMIT;
END;
Run Code Online (Sandbox Code Playgroud)

首先 - 这是做我认为的 - 一次批量提交20万次?假设它是,我仍然不确定生成550万个SQL语句,批量提交200,000,还是有一个SQL语句并一次提交所有更好.

想法?最佳做法?

编辑:我运行了第一个选项,单个删除语句,它只需要2个小时才能完成开发.基于此,它排队等待在生产中运行.

Jir*_*uda 14

第一种方法更好,因为您可以让查询优化器清楚地了解您要执行的操作,而不是尝试隐藏它.数据库引擎可能采用不同的方法在内部删除5.5m(或表的5.5%)而不是删除200k(或0.2%).

这里还有一篇关于Oracle中大量DELETE 的文章,您可能希望阅读它.

  • 但是你必须有大量的撤销空间来匹配,这就是为什么我们中的一些人被迫进行批量提交 (2认同)
  • 如果你能摆脱大热门,当然,去吧。不过,根据工作负载和可用资源,分批提交仍有很多理由。人们经常会看到具有自动扩展到最大值的撤消数据文件的微不足道的数据库,通常就在一个同样大小的表空间旁边,其中大量的审计跟踪不再存在。 (2认同)

Fer*_*anB 8

最快的方法是CREATE TABLE AS SELECT使用NOLOGGING选项创建一个新的方法.我的意思是:

ALTER TABLE table_to_delete RENAME TO tmp;
CREATE TABLE table_to_delete NOLOGGING AS SELECT .... ;
Run Code Online (Sandbox Code Playgroud)

当然,你必须重新创建没有验证的约束,带有nologging,grants的索引......但是非常非常快.

如果您在生产中遇到麻烦,可以执行以下操作:

ALTER TABLE table_to_delete RENAME to tmp;
CREATE VIEW table_to_delete AS SELECT * FROM tmp;
-- Until there can be instantly
CREATE TABLE new_table NOLOGGING AS SELECT .... FROM tmp WHERE ...;
<create indexes with nologging>
<create constraints with novalidate>
<create other things...>
-- From here ...
DROP VIEW table_to_delete;
ALTER TABLE new_table RENAME TO table_to_delete;
-- To here, also instantly
Run Code Online (Sandbox Code Playgroud)

你有照顾:

  • 存储过程可以无效,但是第二次调用时将重新编译它们.你必须测试它.
  • NOLOGGING表示生成最小重做.如果您具有DBA角色,请运行a ALTER SYSTEM CHECKPOINT以确保在实例崩溃时不会丢失数据.
  • 因为NOLOGGING表空间也必须在NOLOGGING.

创建数百万个插件的另一个选择是:

-- Create table with ids
DELETE FROM table_to_delete
 WHERE ID in (SELECT ID FROM table_with_ids WHERE ROWNUM < 100000);
DELETE FROM table_with_ids WHERE ROWNUM < 100000;
COMMIT;
-- Run this 50 times ;-)
Run Code Online (Sandbox Code Playgroud)

PLSQL的选择是不可取的,因为可以创建Snapshot太旧的消息,因为您要使用打开的游标(循环的游标)提交(并关闭事务).Oracle允许它,但这不是一个好习惯.

更新:为什么我可以确保最后的PLSQL块工作?因为我说:

  • 没有其他人出于任何原因使用此临时表(dba或收集统计信息的作业,dab任务,如移动,插入记录等).这是可以确保的,因为它只是一个辅助表.
  • 然后,在最后一个断言中,查询将完全使用相同的计划执行,并将返回具有相同顺序的行.

  • 您如何知道'SELECT ID FROM table_with_ids WHERE ROWNUM <100000'选择与'DELETE FROM table_with_ids WHERE ROWNUM <100000'相同的ID将被删除?你肯定不知道,可以吗? (2认同)

Qua*_*noi 7

在执行大量删除时Oracle,请确保您没有用完UNDO SEGMENTS.

执行时DML,Oracle首先将所有更改写入REDO日志(旧数据和新数据).

REDO日志被填满或发生超时时,Oracle执行log synchronization:它将new数据写入数据文件(在您的情况下,将数据文件块标记为空闲),并将旧数据写入UNDO表空间(以便它对于并发事务保持可见,直到您commit你的改变).

提交更改时,将UNDO释放yuor事务占用的段中的空间.

这意味着如果删除5M数据行,则需要为段中的all这些行留出空间,UNDO以便数据可以先移动到那里(all at once)并在提交后删除.

这也意味着在执行表扫描时,并发查询(如果有)将需要从REDO日志或UNDO段中读取.这不是访问数据的最快方式.

这也意味着如果优化器将选择HASH JOIN你的删除查询(它很可能会这样做),并且临时表将不适合HASH_AREA_SIZE(最可能是这种情况),那么查询将需要several扫描大表格,表格的某些部分已经移入REDOUNDO.

鉴于上述所有内容,您可能最好删除200,000块中的数据并在两者之间提交更改.

因此,您将首先摆脱上述问题,其次,优化您的HASH_JOIN,因为您将具有相同数量的读取,但读取本身将更有效.

但是,在你的情况下,我会尝试强制使用优化器NESTED LOOPS,因为我预计在你的情况下会更快.

为此,请确保您的临时表上有主键ID,并按如下所示重写您的查询:

DELETE  
FROM   (
       SELECT  /*+ USE_NL(tt, tn) */
               tn.id
       FROM    temp_table tt, table_name tn
       WHERE   tn.id = tt.id
       )
Run Code Online (Sandbox Code Playgroud)

您需要拥有主键temp_table才能使此查询生效.

将其与以下内容进行比较:

DELETE  
FROM   (
       SELECT  /*+ USE_HASH(tn tt) */
               tn.id
       FROM    temp_table tt, table_name tn
       WHERE   tn.id = tt.id
       )
Run Code Online (Sandbox Code Playgroud)

,看看什么是更快,并坚持这一点.


Jon*_*son 6

最好像第一个例子那样一次完成所有事情.但我肯定会先用你的DBA来讨论它,因为他们可能想要收回你在清除后不再使用的块.此外,可能存在通常从用户角度看不到的调度问题.