为什么执行delete语句需要比插入更长的时间?
据我所知,删除触发索引和重做更改以及数据块。删除也是如此。
因此我认为两个语句在同一个表上的执行时间相似。但完全不同。是什么造成了这种差异?
作为参考,dbms供应商是Oracle。该表没有触发器,两个索引绑定。
这只是一个简单的删除删除,where cdate>201411
。
答案 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)