为什么要同时使用 TRUNCATE 和 DROP?

use*_*723 105 sql-server sybase truncate

在我工作的系统中有很多使用临时表的存储过程和 SQL 脚本。使用这些表后,最好删除它们。

我的许多同事(几乎所有人都比我更有经验)通常会这样做:

TRUNCATE TABLE #mytemp
DROP TABLE #mytemp
Run Code Online (Sandbox Code Playgroud)

我通常DROP TABLE在我的脚本中使用单个。

在 aTRUNCATE之前立即执行 a 有什么好的理由DROP吗?

Nic*_*mas 140

不。

TRUNCATE并且DROP在行为和速度上几乎相同,所以在 aTRUNCATE之前做一个权利DROP是完全没有必要的。


注意:我从 SQL Server 的角度写了这个答案,并假设它同样适用于 Sybase。看来情况并非完全如此

注意:当我第一次发布这个答案时,还有其他几个评价很高的答案——包括当时被接受的答案——做出了几个错误的声明,比如:TRUNCATE没有记录;TRUNCATE不能回滚;TRUNCATE快于DROP; 等等。

既然这条线索已经清理干净,接下来的反驳似乎与原始问题无关。我将它们留在这里作为其他人希望揭穿这些神话的参考。


有几个流行的谎言——即使在有经验的 DBA 中也很普遍——可能激发了这种TRUNCATE-then-DROP模式。他们是:

  • 神话TRUNCATE未记录,因此无法回滚。
  • 误区TRUNCATEDROP.

让我反驳这些谎言。我是从 SQL Server 的角度写这篇反驳的,但我在这里所说的一切都应该同样适用于 Sybase。

TRUNCATE 记录,并且可以回滚。

  • TRUNCATE是一个记录的操作,因此可以被回滚只需将其包装在交易中即可。

    USE [tempdb];
    SET NOCOUNT ON;
    
    CREATE TABLE truncate_demo (
        whatever    VARCHAR(10)
    );
    
    INSERT INTO truncate_demo (whatever)
    VALUES ('log this');
    
    BEGIN TRANSACTION;
        TRUNCATE TABLE truncate_demo;
    ROLLBACK TRANSACTION;
    
    SELECT *
    FROM truncate_demo;
    
    DROP TABLE truncate_demo;
    
    Run Code Online (Sandbox Code Playgroud)

    但是请注意,不适用于 Oracle。尽管受到 Oracle 的撤消和重做功能的记录和保护,但用户无法回滚TRUNCATE其他 DDL 语句,因为 Oracle在所有 DDL 语句之前和之后立即发出隐式提交

  • TRUNCATE最少记录,而不是完全记录。这意味着什么?说你TRUNCATE一张桌子。无需将每个已删除的行都放入事务日志中,TRUNCATE只需将它们所在的数据页标记为未分配。这就是为什么它这么快。这也是您无法TRUNCATE使用日志阅读器从事务日志中恢复-ed 表的行的原因。所有你会发现有对释放的数据页的引用。

    将此与DELETE. 如果您DELETE将表中的所有行都提交并提交事务,理论上您仍然可以在事务日志中找到已删除的行并从那里恢复它们。这是因为DELETE将每个删除的行写入事务日志。对于大表,这将使它比TRUNCATE.

DROP和 TRUNCATE 一样快。

  • TRUNCATE,DROP是一个最少记录的操作。 这意味着DROP也可以回滚。这也意味着它的工作方式TRUNCATE. 不是删除单个行,而是DROP将适当的数据页标记为未分配,并将表的元数据标记为已删除
  • 由于TRUNCATEDROP工作方式完全相同,他们跑一样快,因为彼此。 TRUNCATE-ing 之前DROP-ing一个表是没有意义的。如果您不相信我,请在您的开发实例上运行此演示脚本

    在我的带有热缓存的本地机器上,我得到的结果如下:

    table row count: 134,217,728
    
    run#        transaction duration (ms)
          TRUNCATE   TRUNCATE then DROP   DROP
    ==========================================
    01       0               1             4
    02       0              39             1
    03       0               1             1
    04       0               2             1
    05       0               1             1
    06       0              25             1
    07       0               1             1
    08       0               1             1
    09       0               1             1
    10       0              12             1
    ------------------------------------------
    avg      0              8.4           1.3
    
    Run Code Online (Sandbox Code Playgroud)

    因此,对于一个 1.34亿行的表DROPTRUNCATE实际上根本不需要时间。(在冷缓存上,第一次或两次运行大约需要 2-3 秒。)我还认为TRUNCATEthenDROP操作的平均持续时间较长可归因于我本地机器上的负载变化,而不是因为组合在某种程度上神奇地比单个操作差一个数量级。毕竟,它们几乎完全相同。

    如果您对这些操作的日志记录开销的更多细节感兴趣,Martin 对此有一个简单的解释


Mar*_*ith 53

TRUNCATE然后测试DROP与直接执行相比,DROP表明第一种方法实际上略微增加了日志记录开销,因此甚至可能会适得其反。

查看单个日志记录显示TRUNCATE ... DROP版本几乎与DROP版本相同,只是有这些附加条目。

+-----------------+---------------+-------------------------+
|    Operation    |    Context    |      AllocUnitName      |
+-----------------+---------------+-------------------------+
| LOP_COUNT_DELTA | LCX_CLUSTERED | sys.sysallocunits.clust |
| LOP_COUNT_DELTA | LCX_CLUSTERED | sys.sysrowsets.clust    |
| LOP_COUNT_DELTA | LCX_CLUSTERED | sys.sysrscols.clst      |
| LOP_COUNT_DELTA | LCX_CLUSTERED | sys.sysrscols.clst      |
| LOP_HOBT_DDL    | LCX_NULL      | NULL                    |
| LOP_MODIFY_ROW  | LCX_CLUSTERED | sys.sysallocunits.clust |
| LOP_HOBT_DDL    | LCX_NULL      | NULL                    |
| LOP_MODIFY_ROW  | LCX_CLUSTERED | sys.sysrowsets.clust    |
| LOP_LOCK_XACT   | LCX_NULL      | NULL                    |
+-----------------+---------------+-------------------------+
Run Code Online (Sandbox Code Playgroud)

所以第TRUNCATE一个版本最终浪费了一点精力对各种系统表进行了一些更新,如下所示

  • 更新rcmodified中的所有表列sys.sysrscols
  • 更新rcrowssysrowsets
  • 清零pgfirst, pgroot, pgfirstiam, pcused, pcdata,pcreserved输入sys.sysallocunits

只有在下一条语句中删除表时,这些系统表行才会最终被删除。

TRUNCATEvs执行的日志记录的完整分解DROP如下。我还添加DELETE了用于比较目的。

+-------------------+-------------------+--------------------+------------------+-----------+---------------+-------------+------------------+-----------+---------------+-------------+
|                   |                   |                    |                            Bytes                           |                            Count                           |
+-------------------+-------------------+--------------------+------------------+-----------+---------------+-------------+------------------+-----------+---------------+-------------+
| Operation         | Context           | AllocUnitName      | Truncate / Drop  | Drop Only | Truncate Only | Delete Only | Truncate / Drop  | Drop Only | Truncate Only | Delete Only |
+-------------------+-------------------+--------------------+------------------+-----------+---------------+-------------+------------------+-----------+---------------+-------------+
| LOP_BEGIN_XACT    | LCX_NULL          |                    | 132              | 132       | 132           | 132         | 1                | 1         | 1             | 1           |
| LOP_COMMIT_XACT   | LCX_NULL          |                    | 52               | 52        | 52            | 52          | 1                | 1         | 1             | 1           |
| LOP_COUNT_DELTA   | LCX_CLUSTERED     | System Table       | 832              |           | 832           |             | 4                |           | 4             |             |
| LOP_DELETE_ROWS   | LCX_MARK_AS_GHOST | System Table       | 2864             | 2864      |               |             | 22               | 22        |               |             |
| LOP_DELETE_ROWS   | LCX_MARK_AS_GHOST | T                  |                  |           |               | 8108000     |                  |           |               | 1000        |
| LOP_HOBT_DDL      | LCX_NULL          |                    | 108              | 36        | 72            |             | 3                | 1         | 2             |             |
| LOP_LOCK_XACT     | LCX_NULL          |                    | 336              | 296       | 40            |             | 8                | 7         | 1             |             |
| LOP_MODIFY_HEADER | LCX_PFS           | Unknown Alloc Unit | 76               | 76        |               | 76          | 1                | 1         |               | 1           |
| LOP_MODIFY_ROW    | LCX_CLUSTERED     | System Table       | 644              | 348       | 296           |             | 5                | 3         | 2             |             |
| LOP_MODIFY_ROW    | LCX_IAM           | T                  | 800              | 800       | 800           |             | 8                | 8         | 8             |             |
| LOP_MODIFY_ROW    | LCX_PFS           | T                  | 11736            | 11736     | 11736         |             | 133              | 133       | 133           |             |
| LOP_MODIFY_ROW    | LCX_PFS           | Unknown Alloc Unit | 92               | 92        | 92            |             | 1                | 1         | 1             |             |
| LOP_SET_BITS      | LCX_GAM           | T                  | 9000             | 9000      | 9000          |             | 125              | 125       | 125           |             |
| LOP_SET_BITS      | LCX_IAM           | T                  | 9000             | 9000      | 9000          |             | 125              | 125       | 125           |             |
| LOP_SET_BITS      | LCX_PFS           | System Table       | 896              | 896       |               |             | 16               | 16        |               |             |
| LOP_SET_BITS      | LCX_PFS           | T                  |                  |           |               | 56000       |                  |           |               | 1000        |
| LOP_SET_BITS      | LCX_SGAM          | Unknown Alloc Unit | 168              | 224       | 168           |             | 3                | 4         | 3             |             |
+-------------------+-------------------+--------------------+------------------+-----------+---------------+-------------+------------------+-----------+---------------+-------------+
| Total             |                   |                    | 36736            | 35552     | 32220         | 8164260     | 456              | 448       | 406           | 2003        |
+-------------------+-------------------+--------------------+------------------+-----------+---------------+-------------+------------------+-----------+---------------+-------------+
Run Code Online (Sandbox Code Playgroud)

该测试是在具有完整恢复模型的数据库中针对每页一行的 1,000 行表进行的。由于根索引页和 3 个中级索引页,该表总共消耗 1,004 页。

其中 8 个页面是混合区中的单页分配,其余页面分布在 125 个统一区中。8 个单页取消分配显示为 8 个LOP_MODIFY_ROW,LCX_IAM日志条目。125 个盘区释放为LOP_SET_BITS LCX_GAM,LCX_IAM. 这两个操作还需要更新相关PFS页面,因此合并了 133LOP_MODIFY_ROW, LCX_PFS个条目。然后当表被实际删除时,需要从各种系统表中删除关于它的元数据,因此有 22 个系统表LOP_DELETE_ROWS日志条目(如下所示)

+----------------------+--------------+-------------------+-------------------+
|        Object        | Rows Deleted | Number of Indexes | Delete Operations |
+----------------------+--------------+-------------------+-------------------+
| sys.sysallocunits    |            1 |                 2 |                 2 |
| sys.syscolpars       |            2 |                 2 |                 4 |
| sys.sysidxstats      |            1 |                 2 |                 2 |
| sys.sysiscols        |            1 |                 2 |                 2 |
| sys.sysobjvalues     |            1 |                 1 |                 1 |
| sys.sysrowsets       |            1 |                 1 |                 1 |
| sys.sysrscols        |            2 |                 1 |                 2 |
| sys.sysschobjs       |            2 |                 4 |                 8 |
+----------------------+--------------+-------------------+-------------------+
|                      |              |                   |                22 |
+----------------------+--------------+-------------------+-------------------+
Run Code Online (Sandbox Code Playgroud)

完整脚本如下

DECLARE @Results TABLE
(
    Testing int NOT NULL,
    Operation nvarchar(31) NOT NULL,
    Context nvarchar(31)  NULL,
    AllocUnitName nvarchar(1000) NULL,
    SumLen int NULL,
    Cnt int NULL
)

DECLARE @I INT = 1

WHILE @I <= 4
BEGIN
IF OBJECT_ID('T','U') IS NULL
     CREATE TABLE T(N INT PRIMARY KEY,Filler char(8000) NULL)

INSERT INTO T(N)
SELECT DISTINCT TOP 1000 number
FROM master..spt_values


CHECKPOINT

DECLARE @allocation_unit_id BIGINT

SELECT @allocation_unit_id = allocation_unit_id
FROM   sys.partitions AS p
       INNER JOIN sys.allocation_units AS a
         ON p.hobt_id = a.container_id
WHERE  p.object_id = object_id('T')  

DECLARE @LSN NVARCHAR(25)
DECLARE @LSN_HEX NVARCHAR(25)

SELECT @LSN = MAX([Current LSN])
FROM fn_dblog(null, null)


SELECT @LSN_HEX=
        CAST(CAST(CONVERT(varbinary,SUBSTRING(@LSN, 1, 8),2) AS INT) AS VARCHAR) + ':' +
        CAST(CAST(CONVERT(varbinary,SUBSTRING(@LSN, 10, 8),2) AS INT) AS VARCHAR) + ':' +
        CAST(CAST(CONVERT(varbinary,SUBSTRING(@LSN, 19, 4),2) AS INT) AS VARCHAR)

  BEGIN TRAN
    IF @I = 1
      BEGIN
          TRUNCATE TABLE T

          DROP TABLE T
      END
    ELSE
      IF @I = 2
        BEGIN
            DROP TABLE T
        END
      ELSE
        IF @I = 3
          BEGIN
              TRUNCATE TABLE T
          END  
      ELSE
        IF @I = 4
          BEGIN
              DELETE FROM T
          END                
  COMMIT

INSERT INTO @Results
SELECT @I,
       CASE
         WHEN GROUPING(Operation) = 1 THEN 'Total'
         ELSE Operation
       END,
       Context,
       CASE
         WHEN AllocUnitId = @allocation_unit_id THEN 'T'
         WHEN AllocUnitName LIKE 'sys.%' THEN 'System Table'
         ELSE AllocUnitName
       END,
       COALESCE(SUM([Log Record Length]), 0) AS [Size in Bytes],
       COUNT(*)                              AS Cnt
FROM   fn_dblog(@LSN_HEX, null) AS D
WHERE  [Current LSN] > @LSN  
GROUP BY GROUPING SETS((Operation, Context,
       CASE
         WHEN AllocUnitId = @allocation_unit_id THEN 'T'
         WHEN AllocUnitName LIKE 'sys.%' THEN 'System Table'
         ELSE AllocUnitName
       END),())


SET @I+=1
END 

SELECT Operation,
       Context,
       AllocUnitName,
       AVG(CASE WHEN Testing = 1 THEN SumLen END) AS [Truncate / Drop Bytes],
       AVG(CASE WHEN Testing = 2 THEN SumLen END) AS [Drop Bytes],
       AVG(CASE WHEN Testing = 3 THEN SumLen END) AS [Truncate Bytes],
       AVG(CASE WHEN Testing = 4 THEN SumLen END) AS [Delete Bytes],
       AVG(CASE WHEN Testing = 1 THEN Cnt END) AS [Truncate / Drop Count],
       AVG(CASE WHEN Testing = 2 THEN Cnt END) AS [Drop Count],
       AVG(CASE WHEN Testing = 3 THEN Cnt END) AS [Truncate Count],
       AVG(CASE WHEN Testing = 4 THEN Cnt END) AS [Delete Count]              
FROM   @Results
GROUP  BY Operation,
          Context,
          AllocUnitName   
ORDER BY Operation, Context,AllocUnitName        

DROP TABLE T
Run Code Online (Sandbox Code Playgroud)