为什么删除比插入更长

时间:2015-03-30 05:53:58

标签: sql oracle performance

为什么执行delete语句需要比插入更长的时间?

据我所知,删除触发索引和重做更改以及数据块。删除也是如此。

因此我认为两个语句在同一个表上的执行时间相似。但完全不同。是什么造成了这种差异?

作为参考,dbms供应商是Oracle。该表没有触发器,两个索引绑定。

这只是一个简单的删除删除,where cdate>201411

4 个答案:

答案 0 :(得分:5)

DELETE可能需要比插入更长的时间有几个原因:

主要的一点是DELETE是一个查询:数据库必须找到您要删除的记录。你的陈述是

  

"删除,where cdate>201411。"

如果CDATE被编入索引,则索引范围最多可以扫描。如果它没有编入索引,则表示全表扫描。但是,如果CDATE具有较差的聚类因子,则索引读取可能无法像完整表扫描那样执行。调整DELETE与调整SELECT语句非常相似。

无论哪种方式,阅读都要比插入记录要多得多。为什么数据库会这样做?因为需要撤消。当我们回滚时,数据库使用UNDO表空间中的信息来反转我们的语句。对于INSERT,撤消操作是删除操作,因此它所需要的只是插入行的ROWID。反转DELETE需要重新插入记录,因此整行必须存储在UNDO中。

因此DELETE操作必须检索整行并将其存储在UNDO中。记录越久,UNDO管理层的开销越大。 (相比之下,插入时ROWID是一个微小且固定的开销。)

同样地,(正如@lalitKumar指出的那样)REDO日志卷对于删除来说可能要大得多。 OraFAQ有一些interesting volumetrics here

外键可能会影响插入和删除:插入必须检查引用表中是否存在键,而删除必须检查依赖表中的引用。这会将唯一键查找与非唯一或甚至未索引的列进行比较。

答案 1 :(得分:2)

如果我正确理解了这个问题,您想知道为什么 DELETE 通常需要比 INSERT 更长的时间。

更新添加自动跟踪统计信息以解释原因

<强>原因

INSERT autotrace statistics

SQL> SET AUTOTRACE ON
SQL> INSERT INTO t(A) SELECT LEVEL FROM dual CONNECT BY LEVEL <=1000;

1000 rows created.


Execution Plan
----------------------------------------------------------
Plan hash value: 1236776825

------------------------------------------------------------------------------
| Id  | Operation                     | Name | Rows  | Cost (%CPU)| Time     |
------------------------------------------------------------------------------
|   0 | INSERT STATEMENT              |      |     1 |     2   (0)| 00:00:01 |
|   1 |  LOAD TABLE CONVENTIONAL      | T    |       |            |          |
|*  2 |   CONNECT BY WITHOUT FILTERING|      |       |            |          |
|   3 |    FAST DUAL                  |      |     1 |     2   (0)| 00:00:01 |
------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - filter(LEVEL<=1000)


Statistics
----------------------------------------------------------
         43  recursive calls
         63  db block gets
         32  consistent gets
          0  physical reads
      19748  redo size
        857  bytes sent via SQL*Net to client
        864  bytes received via SQL*Net from client
          3  SQL*Net roundtrips to/from client
          5  sorts (memory)
          0  sorts (disk)
       1000  rows processed

删除自动跟踪统计信息

SQL> SET AUTOTRACE ON
SQL> DELETE FROM t WHERE ROWNUM <= 1000;

1000 rows deleted.


Execution Plan
----------------------------------------------------------
Plan hash value: 325486485

--------------------------------------------------------------------
| Id  | Operation           | Name | Rows  | Cost (%CPU)| Time     |
--------------------------------------------------------------------
|   0 | DELETE STATEMENT    |      |     1 |     3   (0)| 00:00:01 |
|   1 |  DELETE             | T    |       |            |          |
|*  2 |   COUNT STOPKEY     |      |       |            |          |
|   3 |    TABLE ACCESS FULL| T    |     1 |     3   (0)| 00:00:01 |
--------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - filter(ROWNUM<=1000)

Note
-----
   - dynamic statistics used: dynamic sampling (level=2)


Statistics
----------------------------------------------------------
          8  recursive calls
       1036  db block gets
         15  consistent gets
          0  physical reads
     253264  redo size
        859  bytes sent via SQL*Net to client
        835  bytes received via SQL*Net from client
          3  SQL*Net roundtrips to/from client
          2  sorts (memory)
          0  sorts (disk)
       1000  rows processed

SQL>
  

当你删除一行时,整行进入回滚段并且是   也写入重做日志。

     

执行INSERT时,重做大小为   与DELETE相比要少得多。

让我们做一个小测试,我将使用 DBMS_UTILITY.get_time 来比较时间。我将首先使用索引进行测试,另一个没有索引。

设置

SQL> CREATE TABLE t
  2    (A NUMBER
  3    );

Table created.

SQL>

没有索引的INSERT和DELETE:

SQL> SET SERVEROUTPUT ON
SQL>
SQL> DECLARE
  2    l_start NUMBER;
  3    l_loops NUMBER := 100000;
  4  BEGIN
  5
  6    l_start := DBMS_UTILITY.get_time;
  7
  8    FOR i IN 1 .. l_loops
  9    LOOP
 10      INSERT INTO t
 11        (a
 12        ) VALUES
 13        (i
 14        );
 15    END LOOP;
 16
 17    COMMIT;
 18
 19    DBMS_OUTPUT.put_line('Time taken for INSERT =' || (DBMS_UTILITY.get_time - l_start) || ' hsecs');
 20
 21    l_start := DBMS_UTILITY.get_time;
 22
 23    FOR i IN 1 .. l_loops
 24    LOOP
 25      DELETE FROM t WHERE a = i;
 26    END LOOP;
 27
 28    COMMIT;
 29
 30    DBMS_OUTPUT.put_line('Time taken for DELETE =' || (DBMS_UTILITY.get_time - l_start) || ' hsecs');
 31
 32  END;
 33  /
Time taken for INSERT =354 hsecs
Time taken for DELETE =10244 hsecs

PL/SQL procedure successfully completed.

现在,让&#39;首先 TRUNCATE HIGH WATERMARK 设置为零的表格。

SQL> TRUNCATE TABLE t;

Table truncated.

SQL>

创建索引:

SQL> CREATE INDEX a_indx ON t(A);

Index created.

SQL>

收集表统计信息:

SQL> EXEC DBMS_STATS.gather_table_stats('LALIT', 't');

PL/SQL procedure successfully completed.

使用索引INSERT和DELETE:

SQL> DECLARE
  2    l_start NUMBER;
  3    l_loops NUMBER := 100000;
  4  BEGIN
  5
  6    l_start := DBMS_UTILITY.get_time;
  7
  8    FOR i IN 1 .. l_loops
  9    LOOP
 10      INSERT INTO t
 11        (a
 12        ) VALUES
 13        (i
 14        );
 15    END LOOP;
 16
 17    COMMIT;
 18
 19    DBMS_OUTPUT.put_line('Time taken for INSERT =' || (DBMS_UTILITY.get_time - l_start) || ' hsecs');
 20
 21    l_start := DBMS_UTILITY.get_time;
 22
 23    FOR i IN 1 .. l_loops
 24    LOOP
 25      DELETE FROM t WHERE a = i;
 26    END LOOP;
 27
 28    COMMIT;
 29
 30    DBMS_OUTPUT.put_line('Time taken for DELETE =' || (DBMS_UTILITY.get_time - l_start) || ' hsecs');
 31
 32  END;
 33  /
Time taken for INSERT =1112 hsecs
Time taken for DELETE =1474 hsecs

PL/SQL procedure successfully completed.

SQL>

因此,在任何一种情况下, DELETE 操作都需要比 INSERT 更长的时间。

答案 2 :(得分:1)

您的问题类似于已讨论的主题here。我认为答案仍然是实际的。

  

根据经验,我会说更新通常比a更快   删除。

     

如前所述,更新必须做更少的工作然后删除。但   这在很大程度上取决于更新是如何完成的以及表结构如何   对于删除是。

     

以下是数据库在执行每个步骤时将执行的一些步骤   命令。这些步骤并不完整,但它们有助于实现这一目标   图片。

     

更新

     
      
  • 锁定记录/阻止
  •   
  • 执行更新前语句触发器
  •   每行
  • 执行更新前行触发器
  •   
  • 检查列
  • 是否存在(唯一/检查)约束   
  • 更改数据
  •   
  • 如果新值不适合当前块,则为表添加新块(行链接)。

  • 时经常会发生这种情况   
  • 将列值从5个字符更新为1000个字符

  •   
  • 更改/更新此列上的所有索引
  •   每行
  • 执行更新后行触发器
  •   
  • 执行更新后语句触发器

         

    删除

  •   
  • 锁定记录/阻止
  •   
  • 执行before delete语句触发器
  •   每行
  • 执行更新前行触发器
  •   
  • 检查是否有指向此表的FK
  •   
  • 如果是,则在所有详细信息表上选择以查看是否存在子记录
  •   
  • 取决于FK删除子项或生成错误消息
  •   
  • 将该行标记为已删除。根据pctfree和类似的存储参数,该空间可用于将来的插入
  •   
  • 更改此表中的所有索引
  •   
  • 为每一行执行后删除行触发器
  •   
  • 执行after delete语句触发器
  •   
     

这甚至不包括撤消信息

     

一般来说,我认为删除所做的大多数操作都是更多   比更新贵。特别是对于无索引的更新   列中只添加/更改了1个字符。

     

然而,更新/删除通常不是限制因素,但是   在更新之前发生的选择并找到所有行   需要改变。

答案 3 :(得分:0)

如果您要删除所有行,请尽可能考虑使用TRUNCATE。

如果删除使用WHERE子句,请考虑使用索引查找效率(但请记住,更新删除索引也会导致一些开销)

当前正在使用当前对象的会话(可能会针对您的删除记录锁定行)。它等待会议结束或关闭。

回滚段也会花费你的时间。

另外,对于优化大量删除,请参阅:

1。Link 1

2。Link 2