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


100

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

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

TRUNCATE TABLE #mytemp
DROP TABLE #mytemp

私は通常DROP TABLE、スクリプトでシングルを使用します。

TRUNCATE直前に行う理由はありますDROPか?

回答:


130

番号。

TRUNCATEそしてDROP行動と速度がほぼ同一であり、そうするTRUNCATE権利は、前にDROP単に不要です。


注:SQL Serverの観点からこの回答を書きましたが、Sybaseにも同様に当てはまると思います。と思われる、これは全くそうではありません

注:私がこの回答を最初に投稿したとき、当時受け入れられていた回答を含む他のいくつかの高い評価の回答TRUNCATEがありました。TRUNCATEロールバックできません。TRUNCATEよりも高速ですDROP。等

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


このTRUNCATE-then-DROPパターンの動機付けになった可能性のある、いくつかの人気のある偽りがあります-経験豊富なDBAの間でも広まっています。彼らです:

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

これらの虚偽に反論させてください。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;
    

    ただし、これはOracleには当てはまらないことに注意してください。OracleはすべてのDDLステートメントの直前と直後に暗黙的なコミットを発行するため、Oracleの元に戻す機能とやり直し機能、TRUNCATEおよび他のDDLステートメントによって記録および保護されますが、ロールバックできません

  • TRUNCATE完全に記録されるのではなく、最小限に記録されます。どういう意味ですか?TRUNCATEテーブルを言ってください。削除された各行をトランザクションログに入れる代わりに、TRUNCATEそれらが存在するデータページを未割り当てとしてマークするだけです。それがとても速い理由です。これがTRUNCATE、ログリーダーを使用してトランザクションログから-edテーブルの行を回復できない理由でもあります。割り当て解除されたデータページへの参照があります。

    これと比較してくださいDELETE。あなたの場合はDELETE、テーブル内のすべての行およびトランザクションをコミットあなたはまだ、理論的には、トランザクション・ログに削除された行を見つけて、そこからそれらを回復することができます。これDELETEは、削除されたすべての行をトランザクションログに書き込むためです。大きなテーブルの場合、これによりの速度よりもはるかに遅くなりTRUNCATEます。

DROPはTRUNCATEと同じくらい高速です。

  • のようTRUNCATEDROP、最小限のログ操作です。 つまりDROP、ロールバックも可能です。それはまた、それがまったく同じように機能することを意味しTRUNCATEます。個々の行を削除する代わりにDROP、適切なデータページを未割り当てとしてマークし、さらにテーブルのメタデータをdeletedとしてマークします
  • ためTRUNCATEDROP仕事全く同じように、彼らは同じように速く、互いのように実行します。 テーブルTRUNCATE-ingする前に-ingする意味はありませんDROP信じられない場合は、開発インスタンスでこのデモスクリプトを実行してください。

    ウォームキャッシュを備えたローカルマシンでは、次のような結果が得られます。

    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
    

    だから、134のために万人の両方の行のテーブルDROPTRUNCATE効果的にまったく時間がかかりません。(コールドキャッシュ彼らは、最初の実行または2のために約2~3秒かかる。)私はまた、より高い平均期間と信じてTRUNCATE、その後DROPの操作は私のローカルマシン上の負荷変動に起因しているといない組み合わせは魔法のように何らかの形であるため、個々の操作よりも桁違いに悪い。結局のところ、それらはほぼ同じものです。

    これらの操作のロギングオーバーヘッドについて詳しく知りたい場合は、Martinが簡単に説明しています。


52

テストは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最初のバージョンでは、次のようにさまざまなシステムテーブルを更新するために少しの労力を費やしていました。

  • rcmodifiedすべてのテーブル列の更新sys.sysrscols
  • 更新rcrowssysrowsets
  • ゼロアウトpgfirstpgrootpgfirstiampcusedpcdatapcreservedsys.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        |
+-------------------+-------------------+--------------------+------------------+-----------+---------------+-------------+------------------+-----------+---------------+-------------+

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

これらのページのうち8つは、エクステントが混在する単一ページの割り当てであり、残りは125の均等なエクステントに分散しています。8つの単一ページの割り当て解除は、8つのLOP_MODIFY_ROW,LCX_IAMログエントリとして表示されます。としての125エクステントの割り当て解除LOP_SET_BITS LCX_GAM,LCX_IAM。これらの操作は両方とも、関連する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

2

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

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

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

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


1

歴史的視点を追加する...

テーブルを削除するには、いくつかのシステムテーブルを更新する必要があり、通常、これらのシステムテーブルを1つのトランザクションで変更する必要があります( "transを開始、syscolumnsを削除、sysobjectsを削除、コミット")。

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

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

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

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

あなたはそこだけの単一の「tempdbの」データベースだった、といくつかのアプリケーションが作られた(再び、多くの、何年も前に)ことを考慮するとHEAVYにシステム・テーブルでの競合を減らすことができる任意の「ハック」は、その単一の「tempdbの」データベースの使用を「tempdb」は有益でした。時間が経つにつれて状況が改善されました...複数の一時データベース、システムテーブルの行レベルロック、より良い割り当て解除方法など

それまでの間truncate table、コード内に残されていれば、何を使用しても問題はありません。


-2

外部キーを持つテーブルに対してTRUNCATEを実行するのは理にかなっています。ただし、一時テーブルにはDROPで十分です


TRUNCATEは、どういうわけか外部キーの競合を回避しますか?どうやって?
user259412

1
外部キーがあるというエラーを書き込みます
Evgeniy Gribkov

-8

重要なのtruncateは、テーブル内のすべてを単純かつ取消不能に削除することです(データストアエンジンに基づいた一部の技術仕様はわずかに異なる場合があります)-重いロギングなどをスキップします。

drop table変更が行われているときにすべての変更を記録します。したがって、ロギングを最小限に抑え、無駄なシステムのチャーンを減らすために、非常に大きなテーブルが最初に切り捨てられ、その後削除される可能性があると思います。

truncate トランザクションにラップすることができ(これが最も安全なオプションです)、もちろん、操作をロールバックできます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.