删除记录时的性能问题

时间:2015-07-28 14:12:11

标签: oracle oracle11g

我有一个简单的删除语句:

DELETE FROM MY_TABLE WHERE ATTR_NAME='Something'.

这必须删除 6,00,000 行,这需要超过半小时。 我在表格中有三列,其中ID,ATTR_NAME的组合是主键。第三列是CLOB类型。该表包含 2100万条记录。任何列都没有单独的索引。没有触发器,也没有外键引用。

这不是一次性过程。我需要定期做。

我怀疑这是因为primary key反而会创建索引,从而导致更多时间。如果我错了,请纠正我。我应该尝试删除PK,还是禁用索引?我听说我应该在插入和删除时禁用索引。我不能简单地测试,因为这是生产机器,我需要请求删除权限。请分享您宝贵的建议

总的来说,索引会影响所有DML语句吗?

3 个答案:

答案 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,但它避免了其他方法的这些问题:

  1. 如果DELETE影响29%的数据,索引访问路径几乎肯定无济于事。
  2. 删除并重新创建主键需要花费时间,锁定表格,获得准确的DDL并不总是微不足道。
  3. 由于CLOB列,常规并行DML不起作用。
  4. 分区或软删除需要更改表结构。 (尽管可能这些可能是最快的方法。)

答案 2 :(得分:1)

你有很多选择来调整这些陈述。

  • 分区表

    如果ATTR_NAME列值很少(我猜是来自你的语句)你可以考虑对表进行分区(包括CLOB - 假设CLOB不是内联的),并且可以简单地删除分区。您可能必须将索引重新组织为本地索引。

  • 禁用索引并在DELETE

    之后重建

    我怀疑这确实无济于事 - 是的还有开销 保持指数但600K并不是很多。丢弃并重新创建 应该避免索引。

  • CTAS + Parallelism + DROP / RENAME + RECREATE INDEX

    如果你有一个窗口让数据库离线很短的时间,上面的工作就可以了。

我想尝试将这些记录的CLOB列更新为NULL并随后发出删除选项。这纯粹是为了衡量CLOB列是否占用了执行权。

相关问题