web-dev-qa-db-ja.com

TRUNCATEとDROPの両方を使用する理由

私が取り組んでいるシステムには、一時テーブルを利用する多くのストアドプロシージャとSQLスクリプトがあります。これらのテーブルを使用した後は、削除することをお勧めします。

私の同僚の多く(ほとんどすべての人が私よりもはるかに経験が豊富です)は通常これを行います。

TRUNCATE TABLE #mytemp
DROP TABLE #mytemp

通常、スクリプトでは単一のDROP TABLEを使用します。

TRUNCATEの直前にDROPを行う正当な理由はありますか?

102
user606723

いいえ

TRUNCATEDROPの動作と速度はほぼ同じであるため、TRUNCATEの直前にDROPを実行する必要はありません。


注:SQL Serverの観点からこの回答を書き、Sybaseにも同様に当てはまると想定しました。 これは完全にそうではない

注:この回答を最初に投稿したとき、他にいくつかの高い評価の回答がありました-当時受け入れられていた回答を含めて、次のようないくつかの誤った主張をしました:TRUNCATEはログに記録されませんTRUNCATEはロールバックできません。 TRUNCATEDROPより高速です。等。

このスレッドがクリーンアップされたので、その後の反論は元の質問に接しているように見えるかもしれません。これらの神話をだまそうとしている他の人のための参照としてここに残します。


このTRUNCATE-then-DROPパターンの動機となった可能性のある、よく知られた虚偽がいくつかあります-経験豊富なDBAの間でも蔓延しています。彼らです:

  • 神話TRUNCATEはログに記録されないため、ロールバックできません。
  • 神話TRUNCATEDROPより高速です。

これらの虚偽を反省させてください。この反論はSQL Serverの観点から書いていますが、ここで言うことはすべて、Sybaseにも同様に適用できるはずです。

[〜#〜] truncate [〜#〜]isログに記録され、canロールバックされます。

  • TRUNCATEはログに記録された操作なので、 itcanロールバックされますトランザクションでラップするだけです。

    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;
    

    ただし、これは not true for 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
    

    したがって、134million行テーブルの場合、DROPTRUNCATEのどちらも、事実上まったく時間をかけません。 (コールドキャッシュでは、最初の実行に2〜3秒かかります。)また、TRUNCATEよりもDROPオペレーションの平均継続時間が長いのは、ローカルマシンの負荷変動とnot組み合わせはどういうわけか、魔法のように個々の操作よりも桁違いに悪いので。結局のところ、ほとんど同じものです。

    これらの操作のロギングオーバーヘッドの詳細に興味がある場合は、 Martinが簡単に説明しています です。

132
Nick Chammas

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                    |
+-----------------+---------------+-------------------------+

したがって、TRUNCATEの最初のバージョンは、さまざまなシステムテーブルを次のように更新するために少し労力を費やすことになります

  • sys.sysrscolsのすべてのテーブル列のrcmodifiedを更新します
  • rcrowssysrowsetsを更新
  • ゼロアウトpgfirstpgrootpgfirstiampcusedpcdatapcreserved in sys.sysallocunits

これらのシステムテーブルの行は、テーブルが次のステートメントで削除されたときにのみ削除されます。

TRUNCATEDROPによって実行されるロギングの完全な内訳は以下のとおりです。比較のために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        |
+-------------------+-------------------+--------------------+------------------+-----------+---------------+-------------+------------------+-----------+---------------+-------------+

テストは、ページごとに1行の1,000行のテーブルに対して、完全復旧モデルのデータベースで実行されました。ルートインデックスページと3つの中間レベルインデックスページにより、テーブルは合計で1,004ページを消費します。

これらのページの8つは、混合エクステントの単一ページ割り当てであり、残りは125の均一エクステントに分散されています。 8つの単一ページの割り当て解除は、8つのLOP_MODIFY_ROW,LCX_IAMログエントリとして表示されます。 LOP_SET_BITS LCX_GAM,LCX_IAMとしての125エクステントの割り当て解除。これらの操作はどちらも、関連するPFSページの更新が必要であるため、結合された133 LOP_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 |
+----------------------+--------------+-------------------+-------------------+

下の完全なスクリプト

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
52
Martin Smith

「ウォームキャッシュ」に依存しないいくつかのベンチマークを実行しようと思ったので、それらがより現実的なテストになることを願っています(Postgresを使用して、他の投稿された回答の同じ特性と一致するかどうかを確認します)。 :

大規模なデータベースでpostgres 9.3.4を使用した私のベンチマーク(うまくいけば、RAMキャッシュに収まらない):

このテストDBスクリプトの使用: https://Gist.github.com/rdp/8af84fbb54a430df8fc

1,000万行:

truncate: 1763ms
drop: 2091ms
truncate + drop: 1763ms (truncate) + 300ms (drop) (2063ms total)
drop + recreate: 2063ms (drop) + 242ms (recreate)

1億行:

truncate: 5516ms
truncate + drop: 5592ms
drop: 5680ms (basically, the exact same ballpark)

したがって、これから私は次のことを推測します:ドロップは、truncate + dropと同じくらい(またはそれ以上)高速です(少なくともPostgresの最新バージョンの場合)。ただし、テーブルの向きを変えて再作成する予定の場合はドロップ+再作成よりも高速なストレートトランケートを実行することに固執します(理にかなっています)。 FWIW。

注1: https://stackoverflow.com/questions/11419536/postgresql-truncation-speed/11423886#11423886 (postgres 9.2は以前のバージョンよりも高速に切り捨てられる可能性があると言います)。いつものように、独自のシステムでベンチマークを行い、その特性を確認してください。

注2:トランケートは、トランザクションの場合、postgresでロールバックできます: http://www.postgresql.org/docs/8.4/static/sql-truncate.html

注3:切り捨ては、小さなテーブルでは、削除よりも遅くなることがあります: https://stackoverflow.com/questions/11419536/postgresql-truncation-speed/11423886#11423886

2
rogerdpack

いくつかの歴史的視点を加える...

テーブルを削除するには、いくつかのシステムテーブルを更新する必要があります。そのため、通常、これらのシステムテーブルを単一のトランザクションで変更する必要があります(「トランザクションの開始、syscolumnsの削除、sysobjectsの削除、コミット」と考えてください)。

また、「テーブルのドロップ」には、テーブルに関連付けられているすべてのデータ/インデックスページの割り当てを解除する必要があります。

何年も、何年も前に...スペースの割り当て解除プロセスがトランザクションに含まれていて、システムテーブルも更新されました。最終的に、割り当てられたページの数が多いほど、ページの割り当て解除に時間がかかり、長いほどトランザクション(システムテーブル上)が開いたままになっているため、tempdbでテーブルを作成/ドロップしようとする他のプロセス(システムテーブル上)をブロックする可能性が高くなります(特に、古いallpages ==ページレベルのロックとテーブルの可能性で厄介です)レベルのロックエスカレーション)。

システムテーブルの競合を減らすために(当時)使用された初期の方法の1つは、システムテーブルでロックが保持される時間を短縮することでした。これを行う(比較的)簡単な方法の1つは、ドロップする前にデータ/インデックスページの割り当てを解除することでした。テーブル。

truncate tableは割り当てを解除しませんallデータ/インデックスページ、1つの8ページ(データ)エクステントを除くすべての割り当てを解除します。別の「ハック」は、テーブルを削除する前にすべてのインデックスを削除することでした(そうです、sysindexesのtxnを分離してください。ただし、テーブルの削除のtxnは小さくしてください)。

あなたがそれを(もう一度、何年も前に)単一の「tempdb」データベースがあり、いくつかのアプリケーションが[〜#〜] heavy [〜#〜]その単一の ' tempdbデータベース、「tempdb」内のシステムテーブルの競合を減らすことができる「ハッキング」は有益でした。時間の経過とともに物事は改善しました...複数の一時データベース、システムテーブルの行レベルのロック、より良い割り当て解除方法など。

それまでの間、truncate tableコードに残されていても何も害はありません。

1
markp-fuso