it-swarm.com.de

Warum sowohl TRUNCATE als auch DROP verwenden?

In dem System, an dem ich arbeite, gibt es viele gespeicherte Prozeduren und SQL-Skripte, die temporäre Tabellen verwenden. Nachdem Sie diese Tabellen verwendet haben, sollten Sie sie löschen.

Viele meiner Kollegen (von denen fast alle viel erfahrener sind als ich) tun dies normalerweise:

TRUNCATE TABLE #mytemp
DROP TABLE #mytemp

Ich benutze normalerweise ein einzelnes DROP TABLE in meinen Skripten.

Gibt es einen guten Grund, ein TRUNCATE unmittelbar vor einem DROP auszuführen?

102
user606723

Nein.

TRUNCATE und DROP sind in Verhalten und Geschwindigkeit nahezu identisch, daher ist es einfach, ein TRUNCATE direkt vor einem DROP auszuführen unnötig.


Hinweis: Ich habe diese Antwort aus einer SQL Server-Perspektive geschrieben und angenommen, dass sie auch für Sybase gilt. Es scheint, dass dies ist nicht ganz der Fall .

Hinweis: Als ich diese Antwort zum ersten Mal veröffentlichte, gab es mehrere andere hoch bewertete Antworten - einschließlich der damals akzeptierten Antwort -, die mehrere falsche Behauptungen aufstellten, wie: TRUNCATE ist nicht protokolliert; TRUNCATE kann nicht zurückgesetzt werden; TRUNCATE ist schneller als DROP; usw.

Nachdem dieser Thread bereinigt wurde, scheinen die folgenden Widerlegungen tangential zur ursprünglichen Frage zu sein. Ich lasse sie hier als Referenz für andere, die diese Mythen entlarven wollen.


Es gibt einige populäre Unwahrheiten, die selbst unter erfahrenen Datenbankadministratoren weit verbreitet sind und möglicherweise dieses TRUNCATE-then-DROP - Muster motiviert haben. Sie sind:

  • Mythos : TRUNCATE ist nicht protokolliert und kann daher nicht zurückgesetzt werden.
  • Mythos : TRUNCATE ist schneller als DROP.

Lassen Sie mich diese Unwahrheiten widerlegen. Ich schreibe diese Gegenargumentation aus SQL Server-Sicht, aber alles, was ich hier sage, sollte auch für Sybase gelten.

ABGESCHNITTEN wird protokolliert und kann gewürfelt werden zurück.

  • TRUNCATE ist eine protokollierte Operation, daher kann es zurückgesetzt werden . Wickeln Sie es einfach in eine Transaktion ein.

    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;
    

    Beachten Sie jedoch, dass dies nicht wahr für Oracle ist. Obwohl TRUNCATE und andere DDL-Anweisungen durch die Rückgängig- und Wiederherstellungsfunktion von Oracle protokolliert und geschützt sind, können sie vom Benutzer nicht zurückgesetzt werden, da Oracle implizit) Probleme verursacht Commits unmittelbar vor und nach allen DDL-Anweisungen.

  • TRUNCATE ist minimal protokolliert , im Gegensatz zu vollständig protokolliert. Was bedeutet das? Angenommen, Sie TRUNCATE eine Tabelle. Anstatt jede gelöschte Zeile in das Transaktionsprotokoll aufzunehmen, markiert TRUNCATE nur die Datenseiten, auf denen sie leben, als nicht zugeordnet. Deshalb ist es so schnell. Aus diesem Grund können Sie die Zeilen einer TRUNCATEed-Tabelle nicht mit einem Protokollleser aus dem Transaktionsprotokoll wiederherstellen. Sie finden dort lediglich Verweise auf die freigegebenen Datenseiten.

    Vergleichen Sie dies mit DELETE. Wenn Sie alle Zeilen in einer Tabelle DELETE und die Transaktion festschreiben, können Sie die gelöschten Zeilen theoretisch immer noch im Transaktionsprotokoll finden und von dort wiederherstellen. Das liegt daran, dass DELETE jede gelöschte Zeile in das Transaktionsprotokoll schreibt. Bei großen Tabellen ist dies viel langsamer als bei TRUNCATE.

DROP ist genauso schnell wie TRUNCATE.

  • Wie TRUNCATE ist DROP eine minimal protokollierte Operation. Das bedeutet, dass DROP zurückgesetzt werden kann zu. Das bedeutet auch es funktioniert genauso als TRUNCATE. Anstatt einzelne Zeilen zu löschen, markiert DROP die entsprechenden Datenseiten als nicht zugeordnet und markiert zusätzlich die Metadaten der Tabelle als gelöscht .
  • Da TRUNCATE und DROP genauso funktionieren, laufen sie genauso schnell wie einander. Es macht keinen Sinn, TRUNCATE- eine Tabelle zu erstellen, bevor DROP- sie erstellt. Führen Sie this Demo-Skript auf Ihrer Entwicklungsinstanz, wenn Sie mir nicht glauben.

    Auf meinem lokalen Computer mit einem warmen Cache erhalte ich folgende Ergebnisse:

    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
    

    Für eine Zeilentabelle mit 134 Millionen benötigen sowohl DROP als auch TRUNCATE praktisch keine Zeit. (In einem kalten Cache dauern sie ungefähr 2-3 Sekunden für den ersten oder zweiten Lauf.) Ich glaube auch, dass die höhere durchschnittliche Dauer für die Operation TRUNCATE dann DROP auf Lastschwankungen bei zurückzuführen ist meine lokale Maschine und nicht , weil die Kombination auf magische Weise um eine Größenordnung schlechter ist als die einzelnen Operationen. Sie sind schließlich fast genau dasselbe.

    Wenn Sie mehr über den Protokollierungsaufwand dieser Vorgänge erfahren möchten, Martin hat eine einfache Erklärung davon.

132
Nick Chammas

Das Testen von TRUNCATE und DROP im Vergleich zum direkten Ausführen von DROP zeigt direkt, dass der erste Ansatz tatsächlich einen geringfügig erhöhten Protokollierungsaufwand aufweist und daher möglicherweise sogar leicht kontraproduktiv ist.

Ein Blick auf die einzelnen Protokollsätze zeigt, dass die Version TRUNCATE ... DROP Fast identisch mit der Version DROP ist, außer dass diese zusätzlichen Einträge vorhanden sind.

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

Die erste Version von TRUNCATE verschwendet also ein wenig Mühe, um einige Aktualisierungen an verschiedenen Systemtabellen wie folgt durchzuführen

  • Aktualisieren Sie rcmodified für alle Tabellenspalten in sys.sysrscols
  • Aktualisieren Sie rcrows in sysrowsets
  • Null aus pgfirst, pgroot, pgfirstiam, pcused, pcdata, pcreserved in sys.sysallocunits

Diese Systemtabellenzeilen werden erst gelöscht, wenn die Tabelle in der nächsten Anweisung gelöscht wird.

Eine vollständige Aufschlüsselung der von TRUNCATE vs DROP durchgeführten Protokollierung finden Sie unten. Ich habe zu Vergleichszwecken auch DELETE hinzugefügt.

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

Der Test wurde in einer Datenbank mit vollständigem Wiederherstellungsmodell für eine Tabelle mit 1.000 Zeilen mit einer Zeile pro Seite durchgeführt. Die Tabelle belegt aufgrund der Stammindexseite und 3 Indexseiten der mittleren Ebene insgesamt 1.004 Seiten.

8 dieser Seiten sind Einzelseitenzuordnungen in gemischten Ausmaßen, wobei der Rest auf 125 einheitliche Ausmaße verteilt ist. Die 8 einzelnen Seitenaufhebungen werden als 8 LOP_MODIFY_ROW,LCX_IAM - Protokolleinträge angezeigt. Die 125-Grad-Freigaben als LOP_SET_BITS LCX_GAM,LCX_IAM. Beide Operationen erfordern auch eine Aktualisierung der zugehörigen Seite PFS, daher die kombinierten 133 LOP_MODIFY_ROW, LCX_PFS Einträge. Wenn die Tabelle dann tatsächlich gelöscht wird, müssen die Metadaten darüber aus verschiedenen Systemtabellen entfernt werden, daher die 22 Protokolleinträge der Systemtabelle LOP_DELETE_ROWS (Wie unten angegeben).

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

Vollständiges Skript unten

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

OK, ich dachte, ich würde versuchen, einige Benchmarks durchzuführen, die sich nicht auf "warmes Cacheing" stützen, damit sie hoffentlich ein realistischerer Test sind (auch mit Postgres, um festzustellen, ob er mit den gleichen Merkmalen anderer geposteter Antworten übereinstimmt). ::

Meine Benchmarks mit Postgres 9.3.4 mit einer großen Datenbank (hoffentlich groß genug, um nicht in den Cache zu passen RAM Cache)):

Verwenden dieses Test-DB-Skripts: https://Gist.github.com/rdp/8af84fbb54a430df8fc

mit 10M Zeilen:

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

mit 100M Zeilen:

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

Daraus vermute ich Folgendes: drop ist "ungefähr" so schnell (oder schneller) wie truncate + drop (zumindest für moderne Versionen von Postgres). Wenn Sie jedoch vorhaben, die Tabelle auch umzudrehen und neu zu erstellen, können Sie dies auch tun Halten Sie sich gut an ein gerades Abschneiden, das schneller ist als ein Drop + Recreate (macht Sinn). FWIW.

anmerkung 1: https://stackoverflow.com/questions/11419536/postgresql-truncation-speed/11423886#11423886 (besagt, dass Postgres 9.2 möglicherweise schneller abgeschnitten wird als frühere Versionen). Benchmarking wie immer mit Ihrem eigenen System, um dessen Eigenschaften zu sehen.

anmerkung 2: Abschneiden kann in Postgres zurückgesetzt werden, wenn in einer Transaktion: http://www.postgresql.org/docs/8.4/static/sql-truncate.html

hinweis 3: Das Abschneiden kann bei kleinen Tabellen manchmal langsamer sein als das Löschen: https://stackoverflow.com/questions/11419536/postgresql-truncation-speed/11423886#11423886

2
rogerdpack

Eine historische Perspektive hinzufügen ...

Das Löschen einer Tabelle erfordert das Aktualisieren mehrerer Systemtabellen, was normalerweise erfordert, dass diese Systemtabellenänderungen in einer einzigen Transaktion vorgenommen werden (denken Sie an "tran beginnen, Syscolumns löschen, Sysobjects löschen, Commit").

Ebenfalls in der 'Drop-Tabelle' enthalten ist die Notwendigkeit, alle der Tabelle zugeordneten Daten-/Indexseiten freizugeben.

Vor vielen, vielen, vielen Jahren ... wurde der Prozess der Freigabe von Speicherplatz in die Transaktion einbezogen, mit der auch die Systemtabellen aktualisiert wurden. Das Nettoergebnis war, dass die Freigabe dieser Seiten umso länger dauerte, je größer die Anzahl der zugewiesenen Seiten war Die Transaktion (auf den Systemtabellen) wurde offen gelassen, und daher bestand eine größere Wahrscheinlichkeit, dass (auf Systemtabellen) andere Prozesse blockiert wurden, die versuchten, Tabellen in Tempdb zu erstellen/zu löschen (besonders unangenehm bei älteren Allpages == Sperren auf Seitenebene und Potenzial für Tabellen Eskalation der Sperre).

Eine frühe Methode, die (damals) verwendet wurde, um Konflikte auf den Systemtabellen zu reduzieren, bestand darin, die Zeit zu verkürzen, in der die Sperren für die Systemtabellen gehalten wurden, und eine (relativ) einfache Möglichkeit, dies zu tun, bestand darin, die Zuordnung der Daten-/Indexseiten vor dem Löschen aufzuheben Der Tisch.

Während truncate table gibt die Freigabe nicht auf alle Daten-/Indexseiten, sondern gibt alle bis auf eine 8-seitige (Daten-) Ausdehnung frei; Ein weiterer 'Hack' bestand darin, alle Indizes vor dem Löschen der Tabelle zu löschen (ja, separate txn auf sysindexes, aber eine kleinere txn für drop table).

Wenn man bedenkt, dass es (wieder vor vielen, vielen Jahren) nur die einzige 'tempdb'-Datenbank gab und einige Anwendungen SCHWERE Verwendung dieser Single' tempdb 'Datenbank, alle' Hacks ', die Konflikte auf den Systemtabellen in' tempdb 'reduzieren könnten, waren von Vorteil; Im Laufe der Zeit haben sich die Dinge verbessert ... mehrere temporäre Datenbanken, Sperren auf Zeilenebene für Systemtabellen, bessere Freigabemethoden usw.

In der Zwischenzeit ist die Verwendung des truncate table tut nichts weh, wenn es im Code verbleibt.

1
markp-fuso