我有一个简单的删除语句:
DELETE FROM MY_TABLE WHERE ATTR_NAME='Something'.
这必须删除 6,00,000 行,这需要超过半小时。
我在表格中有三列,其中ID,ATTR_NAME
的组合是主键。第三列是CLOB
类型。该表包含 2100万条记录。任何列都没有单独的索引。没有触发器,也没有外键引用。
这不是一次性过程。我需要定期做。
我怀疑这是因为primary key
反而会创建索引,从而导致更多时间。如果我错了,请纠正我。我应该尝试删除PK,还是禁用索引?我听说我应该在插入和删除时禁用索引。我不能简单地测试,因为这是生产机器,我需要请求删除权限。请分享您宝贵的建议
总的来说,索引会影响所有DML语句吗?
答案 0 :(得分:3)
如果索引为id,attr_name
,则该索引不能用于where
子句,删除查询必须执行全表扫描。
索引字段用于left->right
排序,因此在这些情况下您将使用id,attr_name
索引:
WHERE id = foo AND attr_name = bar
WHERE id = foo
WHERE attr_name = foo AND id = bar // ordering within the where doesn't matter, but USAGE does
但不是
WHERE attr_name = bar
因为id
中没有where
。
您必须在attr_name
上添加专用索引,或者重新排列索引,使其定义为attr_name, id
。当然,如果id
字段是您的主键,它应该已经有一个PK索引,使id, attr_name
多余。
答案 1 :(得分:2)
DBMS_PARALLEL_EXECUTE是一种显着提高效果的简便方法 不改变任何对象或显着改变过程。
示例架构
--Create sample table.
create table my_table(id number, attr_name varchar2(100), a_clob clob);
--Insert 1 million rows. Takes 31 seconds on my PC.
begin
for i in 1 .. 10 loop
insert /*+ append */ into my_table
select level + i*100000, mod(level, 3), rpad('0', 100, '0')
from dual
connect by level <= 100000;
commit;
end loop;
end;
/
--Add primary key.
alter table my_table add constraint my_table_pk primary key (id, attr_name);
简单删除
使用这种简单方法删除1/3的数据需要在我的电脑上完成86秒。
--Flush the cache.
alter system flush buffer_cache;
--Delete 1/3rd of the table.
delete from my_table where attr_name = 0;
rollback;
<强> DBMS_PARALLEL_EXECUTE 强>
并行方法在我的机器上运行速度稍快。希望在具有多个CPU和磁盘的服务器上,差异会更大。这段代码 基于手册中的示例。
--Flush the cache.
alter system flush buffer_cache;
--Delete 1/3rd of the table. Finished in 80 seconds.
begin
--Create the TASK.
dbms_parallel_execute.create_task ('mytask');
--Chunk the table by ROWID.
dbms_parallel_execute.create_chunks_by_rowid(
task_name => 'mytask',
table_owner => user,
table_name => 'MY_TABLE',
by_row => true,
chunk_size => 1000);
--Execute the DML in parallel.
dbms_parallel_execute.run_task(
task_name => 'mytask',
sql_stmt =>
'delete /*+ rowid(my_table) */ from my_table
where rowid BETWEEN :start_id AND :end_id
and attr_name = 0',
language_flag => DBMS_SQL.NATIVE,
parallel_level => 16);
--Get the status.
dbms_output.put_line('Status: '||dbms_parallel_execute.task_status('mytask'));
--Done with processing; drop the task.
dbms_parallel_execute.drop_task('mytask');
end;
/
优点和缺点
此方法需要更多代码才能执行简单的DELETE,但它避免了其他方法的这些问题:
答案 2 :(得分:1)
你有很多选择来调整这些陈述。
分区表
如果ATTR_NAME列值很少(我猜是来自你的语句)你可以考虑对表进行分区(包括CLOB - 假设CLOB不是内联的),并且可以简单地删除分区。您可能必须将索引重新组织为本地索引。
禁用索引并在DELETE
之后重建我怀疑这确实无济于事 - 是的还有开销 保持指数但600K并不是很多。丢弃并重新创建 应该避免索引。
CTAS + Parallelism + DROP / RENAME + RECREATE INDEX
如果你有一个窗口让数据库离线很短的时间,上面的工作就可以了。
我想尝试将这些记录的CLOB列更新为NULL并随后发出删除选项。这纯粹是为了衡量CLOB列是否占用了执行权。