我不是一个数据库人,我的大部分数据库工作都是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 的文章,您可能希望阅读它.
最快的方法是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块工作?因为我说:
在执行大量删除时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
扫描大表格,表格的某些部分已经移入REDO
或UNDO
.
鉴于上述所有内容,您可能最好删除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)
,看看什么是更快,并坚持这一点.