Perché usare sia TRUNCATE che DROP?


100

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

Di solito ne uso uno solo DROP TABLEnei miei script.

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

Risposte:


130

No.

TRUNCATEe DROPsono quasi identici nel comportamento e nella velocità, quindi fare un TRUNCATEdiritto prima di un DROPè semplicemente superfluo.


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 sia 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: TRUNCATEnon è registrato; TRUNCATEnon può essere ripristinato; TRUNCATEè più veloce di DROP; eccetera.

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 cercano di sfatare questi miti.


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

  • Mito : TRUNCATEnon è 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.

TRUNCATE è registrato e può essere ripristinato.

  • TRUNCATEè un'operazione registrata, quindi si può eseguire il rollback . 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 ciò non è vero per Oracle . Sebbene sia registrato e protetto dalla funzionalità di annullamento e ripetizione di Oracle, l'utente non può eseguire il rollback di TRUNCATEaltre istruzioni DDL perché i problemi Oracle implicano il commit immediatamente prima e dopo tutte le istruzioni DDL.

  • TRUNCATEè minimamente registrato , anziché completamente registrato. Cosa significa? Ti dico TRUNCATEun tavolo. Invece di inserire ogni riga eliminata nel registro delle transazioni, TRUNCATEcontrassegna semplicemente 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 TRUNCATEtabella -ed 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 si DELETEinseriscono tutte le righe in una tabella e si esegue il commit della transazione, in teoria è ancora possibile trovare le righe eliminate nel registro delle transazioni e recuperarle da lì. Questo perché DELETEscrive ogni riga eliminata nel registro delle transazioni. Per i tavoli di grandi dimensioni, questo renderà molto più lento di TRUNCATE.

DROP è veloce quanto TRUNCATE.

  • Ad esempio TRUNCATE, DROPè un'operazione minimamente registrata. Ciò significa che DROPpuò anche essere ripristinato. Ciò significa anche che funziona esattamente allo stesso modo di TRUNCATE. Invece di eliminare singole righe, DROPcontrassegna le pagine di dati appropriate come non allocate e contrassegna inoltre i metadati della tabella come eliminati .
  • Perché TRUNCATEe DROPfunzionano esattamente allo stesso modo, corrono veloci l'uno rispetto all'altro. Non ha senso TRUNCATEaggiungere un tavolo prima di DROPesso. Esegui questo script demo 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, sia per una tabella di 134 milioni di righe sia DROPper TRUNCATEnon perdere tempo. (In una cache fredda impiegano circa 2-3 secondi per la prima o due ore ). Credo anche che la durata media più elevata per l' operazione TRUNCATEsuccessiva DROPsia attribuibile alle variazioni del carico sul mio computer locale e non 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 chiara di ciò.


52

Test TRUNCATEquindi DROPvs solo facendo la DROPmostra direttamente che il primo approccio ha in realtà un leggero aumento in testa la registrazione in modo da può anche essere leggermente controproducente.

Guardando i singoli record di registro si mostra che la TRUNCATE ... DROPversione è quasi identica alla DROPversione tranne che per queste 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 TRUNCATEprima versione finisce per sprecare un po 'di sforzo facendo alcuni aggiornamenti a varie tabelle di sistema come segue

  • Aggiornamento rcmodifiedper tutte le colonne della tabella insys.sysrscols
  • Aggiorna rcrowsinsysrowsets
  • Azzerare pgfirst, pgroot, pgfirstiam, pcused, pcdata, pcreservedinsys.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 TRUNCATEvs. DROPHo anche aggiunto DELETEa 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 singole pagine vengono visualizzate come 8 LOP_MODIFY_ROW,LCX_IAMvoci di registro. Le 125 deallocazioni di estensione come LOP_SET_BITS LCX_GAM,LCX_IAM. Entrambe queste operazioni richiedono anche un aggiornamento della PFSpagina associata, quindi le 133 LOP_MODIFY_ROW, LCX_PFSvoci combinate . Quindi, quando la tabella viene effettivamente rilasciata, i metadati su di essa devono essere rimossi da varie tabelle di sistema, quindi le 22 LOP_DELETE_ROWSvoci del registro delle tabelle di sistema (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

2

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 nella cache RAM):

Utilizzando questo script DB di prova: https://gist.github.com/rdp/8af84fbb54a430df8fc0

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 resta fedele a fare un troncato dritto, che è più veloce di una goccia + ricrea (ha 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: troncare può, con tavolini, a volte essere più lento di una cancellazione: https://stackoverflow.com/questions/11419536/postgresql-truncation-speed/11423886#11423886


1

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.

Anche truncate tablese non distribuisce tutte le pagine di dati / indice, distribuisce tutte tranne un'estensione di 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 si considera che (ancora, molti, molti anni fa) esisteva solo il singolo database "tempdb" e alcune applicazioni utilizzavano PESANTE 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 tablenon fa male a nulla se lasciato nel codice.


-2

Ha senso eseguire TRUNCATE per tabelle con chiavi esterne. Tuttavia, per le tabelle temporanee è sufficiente DROP


TRUNCATE eviterebbe in qualche modo un conflitto di chiave esterna? Come?
user259412

1
Scriverà un errore che ci sono chiavi esterne
Evgeniy Gribkov il

-8

Il punto truncateè rimuovere semplicemente e irrevocabilmente tutto nella tabella (alcune specifiche tecniche basate sui motori di archiviazione dei dati possono differire leggermente) - saltare la registrazione pesante, ecc.

drop tableregistra tutte le modifiche mentre vengono apportate le modifiche. Quindi, per avere una registrazione minima e ridurre il churn di sistema inutile, sospetterei che una tabella molto grande possa essere prima troncata, quindi eliminata.

truncate può essere incluso in una transazione (che sarebbe l'opzione più sicura) che, ovviamente, ti consentirà di ripristinare l'operazione.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.