Warum sollten Sie sowohl TRUNCATE als auch DROP verwenden?


100

In dem System, an dem ich arbeite, gibt es viele gespeicherte Prozeduren und SQL-Skripten, die temporäre Tabellen verwenden. Nachdem Sie diese Tabellen verwendet haben, ist es ratsam, sie fallen zu lassen.

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

TRUNCATE TABLE #mytemp
DROP TABLE #mytemp

Normalerweise verwende ich DROP TABLEin meinen Skripten eine einzelne .

Gibt es einen guten Grund, ein TRUNCATEunmittelbar vor einem zu machen DROP?

Antworten:


130

Nein.

TRUNCATEund DROPsind in Verhalten und Geschwindigkeit nahezu identisch, so dass es einfach nicht erforderlich ist, ein TRUNCATERecht vor einem zu tun DROP.


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

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

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


Es gibt ein paar populäre Lügen, die selbst unter erfahrenen Datenbankadministratoren allgegenwärtig sind und dieses TRUNCATE-then-DROPMuster möglicherweise motiviert haben . Sie sind:

  • Mythos : TRUNCATEist nicht protokolliert, daher kann kein Rollback durchgeführt werden.
  • Mythos : TRUNCATEist schneller als DROP.

Lassen Sie mich diese Lügen widerlegen. Ich schreibe diese Gegenargumentation aus SQL Server-Sicht, aber alles, was ich hier sage, sollte auch auf Sybase zutreffen.

TRUNCATE wird protokolliert und kann zurückgesetzt werden.

  • TRUNCATEBetrieb ist ein angemeldet, so es kann rückgängig gemacht 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 für Oracle nicht zutrifft . Oracle wird zwar durch die Rückgängig- und Wiederherstellungsfunktionen von Oracle protokolliert und ist durch diese geschützt. TRUNCATEAndere DDL-Anweisungen können vom Benutzer nicht zurückgesetzt werden, da Oracle implizite Commits unmittelbar vor und nach allen DDL-Anweisungen ausgibt .

  • TRUNCATEwird im Gegensatz zu vollständig protokolliert minimal protokolliert. Was bedeutet das? Sag dir TRUNCATEeinen Tisch. Anstatt jede gelöschte Zeile in das Transaktionsprotokoll aufzunehmen, TRUNCATEmarkieren Sie einfach die Datenseiten, auf denen sie sich befinden, als nicht zugeordnet. Deshalb ist es so schnell. Das ist auch der Grund, warum Sie die Zeilen einer TRUNCATE-ed-Tabelle nicht mit einem Protokollleser aus dem Transaktionsprotokoll wiederherstellen können . Alles, was Sie dort finden, sind Verweise auf die freigegebenen Datenseiten.

    Vergleichen Sie dies mit DELETE. Wenn Sie DELETEalle Zeilen in einer Tabelle speichern und die Transaktion festschreiben, können Sie theoretisch immer noch die gelöschten Zeilen im Transaktionsprotokoll suchen und von dort wiederherstellen. Das liegt daran DELETE, dass jede gelöschte Zeile in das Transaktionslog geschrieben wird. Bei großen Tischen wird dies viel langsamer als TRUNCATE.

DROP ist genauso schnell wie TRUNCATE.

  • Gefällt mir TRUNCATE, DROPist eine minimal protokollierte Operation. Das heißt, es DROPkann auch ein Rollback durchgeführt werden. Das heißt auch, dass es genauso funktioniert wie TRUNCATE. Markieren Sie anstelle des Löschens einzelner Zeilen DROPdie entsprechenden Datenseiten als nicht zugeordnet und markieren Sie zusätzlich die Metadaten der Tabelle als gelöscht .
  • Weil TRUNCATEund DROPgenau so arbeiten, laufen sie genauso schnell wie die anderen. Es macht keinen Sinn, TRUNCATEeinen Tisch vorher zu DROP-en. Führen Sie dieses Demo-Skript auf Ihrer Entwicklungsinstanz aus, wenn Sie mir nicht glauben.

    Auf meinem lokalen Computer mit einem warmen Cache erhalte ich die folgenden 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

    Also, für eine 134- Millionen- Zeilentabelle beide DROPund TRUNCATEeffektiv überhaupt keine Zeit in Anspruch nehmen. (An einem kalten Cache nehmen sie etwa 2-3 Sekunden für den ersten Lauf oder zwei.) Ich glaube auch , dass die höhere durchschnittliche Dauer für den TRUNCATEdann den DROPBetrieb zurückzuführen Variationen auf meinem lokalen Rechner zu laden und nicht , weil die Kombination ist irgendwie magisch ein Größenordnung schlechter als die einzelnen Operationen. Sie sind schließlich fast genau dasselbe.

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


52

Wenn Sie TRUNCATEdann DROPnur das DROPdirekte Ausführen testen, zeigt sich, dass der erste Ansatz tatsächlich einen geringfügig erhöhten Protokollierungsaufwand hat und daher möglicherweise sogar geringfügig kontraproduktiv ist.

Wenn Sie sich die einzelnen Protokollsätze ansehen, sehen Sie, dass die TRUNCATE ... DROPVersion fast identisch mit der DROPVersion 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 TRUNCATEerste Version verschwendet also ein wenig Mühe, indem sie wie folgt einige Aktualisierungen für verschiedene Systemtabellen vornimmt

  • Update rcmodifiedfür alle Tabellenspalten insys.sysrscols
  • Aktualisieren Sie rcrowsinsysrowsets
  • Null aus pgfirst, pgroot, pgfirstiam, pcused, pcdata, pcreservedinsys.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 TRUNCATEvs durchgeführten Protokollierung finden Sie weiter DROPunten. Ich habe auch DELETEzu Vergleichszwecken 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 einem vollständigen Wiederherstellungsmodell anhand einer Tabelle mit 1.000 Zeilen und einer Zeile pro Seite durchgeführt. Die Tabelle belegt aufgrund der Stammindexseite und der 3 Indexseiten der Zwischenebene insgesamt 1.004 Seiten.

8 dieser Seiten sind einzelne Seitenzuordnungen in gemischten Ausmaßen, der Rest verteilt sich auf 125 einheitliche Ausmaße. Die 8 Aufhebungen einzelner Seiten werden als 8 LOP_MODIFY_ROW,LCX_IAMProtokolleinträge angezeigt. Die 125-Grad-Aufhebungen als LOP_SET_BITS LCX_GAM,LCX_IAM. Beide Operationen erfordern auch eine Aktualisierung der zugeordneten PFSSeite, daher die kombinierten 133 LOP_MODIFY_ROW, LCX_PFSEinträge. Wenn die Tabelle dann tatsächlich gelöscht wird, müssen die Metadaten darüber aus verschiedenen Systemtabellen entfernt werden, daher die 22 LOP_DELETE_ROWSSystemtabellenprotokolleinträge (wie nachstehend erklärt).

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

2

OK dachte, ich würde versuchen, einige Benchmarks zu machen, die sich nicht auf "Warm-Cache" stützen, damit sie hoffentlich realistischer sind (auch unter Verwendung von Postgres, um zu sehen, ob sie mit den gleichen Eigenschaften wie andere veröffentlichte Antworten übereinstimmen). :

Meine Benchmarks unter Verwendung von postgres 9.3.4 mit einer umfangreichen Datenbank (hoffentlich groß genug, um nicht in den RAM-Cache zu passen):

Verwenden dieses Test-DB-Skripts: https://gist.github.com/rdp/8af84fbb54a430df8fc0

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)

Ich gehe also davon aus, dass drop "ungefähr" so schnell (oder schneller) ist wie trunk + drop (zumindest für moderne Versionen von Postgres). Wenn Sie jedoch vorhaben, auch die Tabelle umzudrehen und neu zu erstellen, können Sie dies tun Halte dich gut daran, ein gerades Abschneiden zu machen, das schneller ist als ein Ablegen + Wiederherstellen (macht Sinn). FWIW.

Hinweis 1: https://stackoverflow.com/questions/11419536/postgresql-truncation-speed/11423886#11423886 (sagt, dass postgres 9.2 möglicherweise schneller abgeschnitten wird als frühere Versionen). Wie immer sollten Sie ein Benchmarking mit Ihrem eigenen System durchführen, um dessen Eigenschaften zu erkennen.

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

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


1

Eine historische Perspektive hinzufügen ...

Das Löschen einer Tabelle erfordert das Aktualisieren mehrerer Systemtabellen, was wiederum in der Regel das Vornehmen dieser Systemtabellenänderungen in einer einzigen Transaktion erfordert (denken Sie an "begin tran, delete syscolumns, delete sysobjects, commit").

Ebenfalls in der Drop-Tabelle enthalten ist die Notwendigkeit, die Zuordnung aller der Tabelle zugeordneten Daten- / Indexseiten aufzuheben.

Vor vielen, vielen, vielen Jahren ... war der Speicherplatzfreigabevorgang in der Transaktion enthalten, mit der auch die Systemtabellen aktualisiert wurden: Je mehr Seiten zugewiesen wurden, desto länger dauerte die Freigabe der Seiten Die Transaktion (für die Systemtabellen) wurde offen gelassen, und daher war die Wahrscheinlichkeit größer, dass (für Systemtabellen) andere Prozesse blockiert wurden, die versuchten, Tabellen in Tempdb zu erstellen / zu löschen (besonders unangenehm bei älteren allpages == Sperren auf Seitenebene und potenziellen Tabellen -Level Lock Eskalation).

Eine frühe Methode zur Reduzierung von Konflikten auf den Systemtabellen bestand darin, die Zeit zu verkürzen, in der die Sperren für die Systemtabellen gehalten wurden, und eine (relativ) einfache Möglichkeit bestand darin, die Zuordnung der Daten- / Indexseiten vor dem Löschen aufzuheben Der Tisch.

Während truncate tablenicht alle Daten- / Indexseiten freigegeben werden, werden alle bis auf einen Umfang von 8 Seiten (Daten) freigegeben. Ein weiterer "Hack" bestand darin, alle Indizes zu löschen, bevor die Tabelle gelöscht wurde (ja, separate txn für sysindexes, aber eine kleinere txn für drop table).

Wenn man bedenkt , dass (auch hier vor vielen, vielen Jahren) gibt es nur die Single ‚Tempdb‘ Datenbank war, und einige Anwendungen aus HEAVY Verwendung dieses einzigen ‚Tempdb‘ Datenbank, alle ‚Hacks‘ , die Behauptung auf den Systemtabellen in verringern könnte 'tempdb' 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 truncate tableschadet die Verwendung von nichts, wenn sie im Code verbleibt.


-2

Es ist sinnvoll, TRUNCATE für Tabellen mit Fremdschlüsseln auszuführen. Für temporäre Tabellen reicht jedoch DROP aus


TRUNCATE würde irgendwie einen Fremdschlüsselkonflikt vermeiden? Wie?
user259412

1
Wird einen Fehler schreiben, dass es Fremdschlüssel gibt
Evgeniy

-8

Der Zweck von truncateist, einfach und unwiderruflich alles in der Tabelle zu entfernen (einige technische Details, die auf Datenspeicher-Engines basieren, können sich geringfügig unterscheiden) - Überspringen von starker Protokollierung usw.

drop tableprotokolliert alle Änderungen, während die Änderungen vorgenommen werden. Um die Protokollierung auf ein Minimum zu beschränken und unnötige Systemabwanderung zu reduzieren, würde ich vermuten, dass eine sehr große Tabelle zuerst abgeschnitten und dann gelöscht werden könnte.

truncate kann in eine Transaktion eingebunden sein (was die sicherste Option wäre), die es Ihnen natürlich ermöglicht, den Vorgang rückgängig zu machen.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.