it-swarm.it

Perché usare sia TRUNCATE che DROP?

Nel sistema su cui lavoro ci sono molte stored procedure e script SQL che utilizzano tabelle temporanee. Dopo aver usato queste tabelle è buona pratica lasciarle cadere.

Molti dei miei colleghi (quasi tutti molto più esperti di me) in genere lo fanno:

TRUNCATE TABLE #mytemp
DROP TABLE #mytemp

In genere uso un singolo DROP TABLE nei miei script.

C'è qualche buona ragione per fare un TRUNCATE immediatamente prima di un DROP?

102
user606723

n.

TRUNCATE e DROP sono quasi identici nel comportamento e nella velocità, quindi fare un TRUNCATE prima di un DROP è semplicemente . inutili


Nota: ho scritto questa risposta dal punto di vista di SQL Server e ho pensato che si sarebbe applicato allo stesso modo a Sybase. Sembra che non è del tutto vero .

Nota: quando ho pubblicato per la prima volta questa risposta, c'erano diverse altre risposte molto apprezzate - inclusa la risposta allora accettata - che hanno fatto diverse affermazioni false come: TRUNCATE non è registrato; TRUNCATE non può essere ripristinato; TRUNCATE è più veloce di DROP; ecc.

Ora che questo thread è stato ripulito, le confutazioni che seguono potrebbero sembrare tangenziali alla domanda originale. Li lascio qui come riferimento per gli altri che vogliono sfatare questi miti.


Ci sono un paio di falsità popolari - pervasive anche tra i DBA esperti - che possono aver motivato questo TRUNCATE-then-DROP modello. Loro sono:

  • Mito : TRUNCATE non è registrato, quindi non può essere ripristinato.
  • Mito : TRUNCATE è più veloce di DROP.

Vorrei confutare queste menzogne. Sto scrivendo questa confutazione dal punto di vista di SQL Server, ma tutto ciò che dico qui dovrebbe essere ugualmente applicabile a Sybase.

TRONCA è registrato, e può può essere ripristinato.

  • TRUNCATE è un'operazione registrata, quindi esso can può essere ripristinato . Basta avvolgerlo in una transazione.

    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;
    

    Si noti, tuttavia, che questo è non vero per Oracle . Sebbene sia stato registrato e protetto dalla funzionalità di annullamento e ripetizione di Oracle, TRUNCATE e altre istruzioni DDL impossibile devono essere ripristinati dall'utente a causa di problemi di Oracle commit impliciti = immediatamente prima e dopo tutte le istruzioni DDL.

  • TRUNCATE è minimamente registrato , al contrario di completamente registrato. Cosa significa? Di 'TRUNCATE una tabella. Invece di inserire ogni riga eliminata nel registro delle transazioni, TRUNCATE contrassegna le pagine di dati in cui vivono come non allocate. Ecco perché è così veloce. Questo è anche il motivo per cui non è possibile recuperare le righe di una tabella TRUNCATE dal registro delle transazioni utilizzando un lettore di registri. Tutto ciò che troverai ci sono riferimenti alle pagine di dati deallocati.

    Confronta questo con DELETE. Se DELETE tutte le righe in una tabella e si impegna la transazione, in teoria è ancora possibile trovare le righe eliminate nel registro delle transazioni e recuperarle da lì. Questo perché DELETE scrive ogni riga eliminata nel registro delle transazioni. Per le tabelle di grandi dimensioni, ciò renderà molto più lento di TRUNCATE.

DROP è veloce quanto TRUNCATE.

  • Come TRUNCATE, DROP è un'operazione minimamente registrata. Ciò significa che DROP può essere ripristinato pure. Ciò significa anche funziona esattamente allo stesso modo di TRUNCATE. Invece di eliminare singole righe, DROP contrassegna le pagine di dati appropriate come non allocate e contrassegna inoltre i metadati della tabella come eliminati .
  • Poiché TRUNCATE e DROP funzionano esattamente allo stesso modo, funzionano alla stessa velocità. Non ha senso TRUNCATE - in una tabella prima di DROP - in essa. Esegui questo demo script sulla tua istanza di sviluppo se non mi credi.

    Sul mio computer locale con una cache calda, i risultati che ottengo sono i seguenti:

    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
    

    Quindi, per una tabella di righe 134 milioni sia DROP che TRUNCATE non impiegano tempo. (In una cache fredda impiegano circa 2-3 secondi per la prima o la prima esecuzione.) Credo anche che la durata media più elevata per l'operazione TRUNCATE quindi DROP sia attribuibile alle variazioni di carico su la mia macchina locale e no perché la combinazione è in qualche modo magicamente un ordine di grandezza peggiore delle singole operazioni. Dopotutto, sono quasi esattamente la stessa cosa.

    Se sei interessato a maggiori dettagli sul sovraccarico di registrazione di queste operazioni, Martin ha una spiegazione semplice di quello.

132
Nick Chammas

Testare TRUNCATE quindi DROP rispetto al semplice fare il DROP mostra direttamente che il primo approccio ha effettivamente un leggero aumento del sovraccarico di registrazione, quindi potrebbe anche essere leggermente controproducente.

Guardando i singoli record di registro si mostra che la versione TRUNCATE ... DROP È quasi identica alla versione DROP tranne per le voci aggiuntive.

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

Quindi la prima versione di TRUNCATE finisce per perdere un po 'di sforzo nel fare alcuni aggiornamenti a varie tabelle di sistema come segue

  • Aggiorna rcmodified per tutte le colonne della tabella in sys.sysrscols
  • Aggiorna rcrows in sysrowsets
  • Azzerare pgfirst, pgroot, pgfirstiam, pcused, pcdata, pcreserved in sys.sysallocunits

Queste righe della tabella di sistema finiscono per essere eliminate solo quando la tabella viene eliminata nell'istruzione successiva.

Di seguito è riportata una suddivisione completa della registrazione effettuata da TRUNCATE vs DROP. Ho anche aggiunto DELETE a scopo di confronto.

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

Il test è stato eseguito in un database con modello di recupero completo su una tabella di 1.000 righe con una riga per pagina. La tabella consuma 1.004 pagine in totale a causa della pagina dell'indice radice e di 3 pagine dell'indice di livello intermedio.

8 di queste pagine sono allocazioni di pagine singole in dimensioni miste con il resto distribuito su 125 Uniform Extents. Le 8 allocazioni di pagine singole vengono visualizzate come 8 voci di registro LOP_MODIFY_ROW,LCX_IAM. Le deallocazioni di 125 estensioni come LOP_SET_BITS LCX_GAM,LCX_IAM. Entrambe queste operazioni richiedono anche un aggiornamento della pagina PFS associata, quindi le voci 133 LOP_MODIFY_ROW, LCX_PFS Combinate. Quindi, quando la tabella viene effettivamente rilasciata, i metadati su di essa devono essere rimossi da varie tabelle di sistema, quindi le voci di registro LOP_DELETE_ROWS Della tabella di sistema 22 (contabilizzate come di seguito)

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

Script completo di seguito

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 ho pensato di provare a fare alcuni benchmark che non si basavano su alcun "warm cacheing" in modo che si spera fossero un test più realistico (usando anche Postgres, per vedere se corrisponde alle stesse caratteristiche delle altre risposte postate) :

I miei benchmark che usano Postgres 9.3.4 con un database di grandi dimensioni (si spera abbastanza grande da non rientrare in RAM cache):

Utilizzando questo script DB di prova: https://Gist.github.com/rdp/8af84fbb54a430df8fc

con 10 M righe:

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

con 100 milioni di righe:

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

Quindi da questo suppongo quanto segue: drop è "circa" più veloce (o più veloce) di truncate + drop (almeno per le versioni moderne di Postgres), tuttavia, se prevedi di girare anche e ricreare la tabella, potresti bene attenersi a fare un troncato dritto, che è più veloce di una goccia + ricreare (ha un senso). FWIW.

nota 1: https://stackoverflow.com/questions/11419536/postgresql-truncation-speed/11423886#11423886 (afferma che Postgres 9.2 potrebbe avere un troncamento più veloce rispetto alle versioni precedenti). Come sempre, fai un benchmark con il tuo sistema per vedere le sue caratteristiche.

nota 2: il troncamento può essere eseguito il rollback in postgres, se in una transazione: http://www.postgresql.org/docs/8.4/static/sql-truncate.html

nota 3: il troncamento può, con tabelle piccole, talvolta essere più lento di un'eliminazione: https://stackoverflow.com/questions/11419536/postgresql-truncation-speed/11423886#11423886

2
rogerdpack

Aggiunta di una prospettiva storica ...

Il rilascio di una tabella richiede l'aggiornamento di più tabelle di sistema, che a loro volta richiedono in genere di apportare queste modifiche alla tabella di sistema in una singola transazione (si pensi "inizia a tran, elimina syscolumns, elimina sysobjects, commit").

Nella "tabella a discesa" è inclusa anche la necessità di deallocare tutte le pagine di dati/indici associate alla tabella.

Molti, molti, molti anni fa ... il processo di deallocazione dello spazio è stato incluso nella transazione che ha anche aggiornato le tabelle di sistema; il risultato netto è stato che maggiore è il numero di pagine allocate, più tempo è stato necessario per deallocare tali pagine, più lungo è il la transazione (sulle tabelle di sistema) è stata lasciata aperta, e quindi una maggiore possibilità di bloccare (sulle tabelle di sistema) altri processi che tentano di creare/eliminare tabelle in tempdb (in particolare con il vecchio blocco allpages == a livello di pagina e il potenziale per la tabella escalation blocco livello).

Un primo metodo utilizzato (fino a quel momento) per ridurre la contesa sulle tabelle di sistema era ridurre il tempo in cui i blocchi venivano mantenuti sulle tabelle di sistema, e un modo (relativamente) semplice per farlo era di deallocare le pagine di dati/indice prima di rilasciare la tavola.

Mentre truncate table non dealloca tutte pagine dati/indice, disloca tutte le dimensioni tranne 8 pagine (dati); un altro 'hack' è stato quindi quello di eliminare tutti gli indici prima di eliminare la tabella (sì, separare txn su sysindexes ma una txn più piccola per drop table).

Se consideri che (ancora, molti, molti anni fa) c'era solo il singolo database 'tempdb' e alcune applicazioni hanno fatto HEAVY uso di quel singolo ' database "tempdb", qualsiasi "hack" che potesse ridurre la contesa sulle tabelle di sistema in "tempdb" era di beneficio; nel tempo le cose sono migliorate ... più database temporanei, blocco a livello di riga su tabelle di sistema, migliori metodi di deallocazione, ecc.

Nel frattempo l'uso del truncate table non danneggia nulla se lasciato nel codice.

1
markp-fuso