web-dev-qa-db-fra.com

Pourquoi utiliser TRUNCATE et DROP?

Dans le système sur lequel je travaille, il existe de nombreuses procédures stockées et scripts SQL qui utilisent des tables temporaires. Après avoir utilisé ces tableaux, il est recommandé de les supprimer.

Beaucoup de mes collègues (qui sont presque tous beaucoup plus expérimentés que moi) le font généralement:

TRUNCATE TABLE #mytemp
DROP TABLE #mytemp

J'utilise généralement un seul DROP TABLE dans mes scripts.

Y a-t-il une bonne raison de faire un TRUNCATE juste avant un DROP?

102
user606723

Non.

TRUNCATE et DROP sont presque identiques en termes de comportement et de vitesse, donc faire un TRUNCATE juste avant un DROP est tout simplement inutile.


Remarque: J'ai écrit cette réponse dans une perspective SQL Server et j'ai supposé qu'elle s'appliquerait également à Sybase. Il semble que ce n'est pas tout à fait le cas .

Remarque: lorsque j'ai publié cette réponse pour la première fois, il y avait plusieurs autres réponses très bien notées - y compris la réponse alors acceptée - qui ont fait plusieurs fausses allégations comme: TRUNCATE n'est pas enregistré; TRUNCATE ne peut pas être annulé; TRUNCATE est plus rapide que DROP; etc.

Maintenant que ce fil a été nettoyé, les réfutations qui suivent peuvent sembler tangentielles à la question d'origine. Je les laisse ici comme référence pour ceux qui cherchent à démystifier ces mythes.


Il y a quelques faussetés populaires - omniprésentes même parmi les administrateurs de bases de données expérimentés - qui peuvent avoir motivé ce TRUNCATE-then-DROP modèle. Elles sont:

  • Mythe : TRUNCATE n'est pas enregistré, il ne peut donc pas être annulé.
  • Mythe : TRUNCATE est plus rapide que DROP.

Permettez-moi de réfuter ces mensonges. J'écris cette réfutation du point de vue de SQL Server, mais tout ce que je dis ici devrait être également applicable à Sybase.

TRONQUER est enregistré, et il peut être annulé.

  • TRUNCATE est une opération enregistrée, donc il peut être annulé . Il suffit de l'envelopper dans une transaction.

    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;
    

    Notez cependant que c'est pas vrai pour Oracle . Bien qu'ils soient journalisés et protégés par les fonctionnalités d'annulation et de rétablissement d'Oracle, TRUNCATE et d'autres instructions DDL ne peuvent pas être annulées par l'utilisateur car Oracle émet validations implicites = immédiatement avant et après toutes les instructions DDL.

  • TRUNCATE est journalisé de façon minimale , par opposition à entièrement journalisé. Qu'est-ce que ça veut dire? Supposons que vous TRUNCATE une table. Au lieu de placer chaque ligne supprimée dans le journal des transactions, TRUNCATE marque simplement les pages de données sur lesquelles elles vivent comme non allouées. C'est pourquoi c'est si rapide. C'est aussi pourquoi vous ne pouvez pas récupérer les lignes d'une table TRUNCATE - ed à partir du journal des transactions à l'aide d'un lecteur de journal. Tout ce que vous y trouverez, ce sont des références aux pages de données désallouées.

    Comparez cela à DELETE. Si vous DELETE toutes les lignes d'une table et validez la transaction, vous pouvez toujours, en théorie, trouver les lignes supprimées dans le journal des transactions et les récupérer à partir de là. En effet, DELETE écrit chaque ligne supprimée dans le journal des transactions. Pour les grandes tables, cela sera beaucoup plus lent que TRUNCATE.

DROP est tout aussi rapide que TRUNCATE.

  • Comme TRUNCATE, DROP est une opération à journalisation minimale. Cela signifie que DROP peut être annulé aussi. Cela signifie également cela fonctionne exactement de la même manière que TRUNCATE. Au lieu de supprimer des lignes individuelles, DROP marque les pages de données appropriées comme non allouées et marque en outre les métadonnées de la table comme supprimées .
  • Comme TRUNCATE et DROP fonctionnent exactement de la même manière, ils s'exécutent aussi vite l'un que l'autre. Il n'y a aucun intérêt à TRUNCATE - ing une table avant DROP - ing. Run this script de démonstration sur votre instance de développement si vous ne me croyez pas.

    Sur ma machine locale avec un cache chaud, les résultats que j'obtiens sont les suivants:

    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
    

    Ainsi, pour une table de lignes 134 million, DROP et TRUNCATE ne prennent en fait aucun temps. (Sur un cache froid, cela prend environ 2-3 secondes pour la première ou les deux premières exécutions.) Je pense également que la durée moyenne plus élevée pour l'opération TRUNCATE puis DROP est attribuable aux variations de charge sur ma machine locale et pas parce que la combinaison est en quelque sorte magiquement un ordre de grandeur pire que les opérations individuelles. Ils sont, après tout, presque exactement la même chose.

    Si vous êtes intéressé par plus de détails sur la surcharge de journalisation de ces opérations, Martin a une explication simple de cela.

132
Nick Chammas

Tester TRUNCATE puis DROP vs simplement faire le DROP montre directement que la première approche a en fait une légère augmentation de la charge de journalisation, ce qui peut même être légèrement contre-productif.

L'examen des enregistrements de journal individuels montre que la version TRUNCATE ... DROP Est presque identique à la version DROP, à l'exception de ces entrées supplémentaires.

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

Donc, la première version de TRUNCATE finit par perdre un peu d'effort à faire quelques mises à jour de diverses tables système comme suit

  • Mettre à jour rcmodified pour toutes les colonnes du tableau dans sys.sysrscols
  • Mettre à jour rcrows dans sysrowsets
  • Zero out pgfirst, pgroot, pgfirstiam, pcused, pcdata, pcreserved in sys.sysallocunits

Ces lignes de table système finissent par être supprimées uniquement lorsque la table est supprimée dans l'instruction suivante.

Une ventilation complète de la journalisation effectuée par TRUNCATE vs DROP est ci-dessous. J'ai également ajouté DELETE à des fins de comparaison.

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

Le test a été effectué dans une base de données avec un modèle de récupération complète sur une table de 1 000 lignes avec une ligne par page. La table consomme 1 004 pages au total en raison de la page d'index racine et de 3 pages d'index de niveau intermédiaire.

8 de ces pages sont des allocations d'une seule page dans des extensions mixtes, le reste étant réparti sur 125 extensions uniformes. Les 8 désallocations de page unique apparaissent comme les 8 entrées de journal LOP_MODIFY_ROW,LCX_IAM. Les 125 désallocations d'étendue comme LOP_SET_BITS LCX_GAM,LCX_IAM. Ces deux opérations nécessitent également une mise à jour de la page PFS associée, d'où les 133 entrées combinées LOP_MODIFY_ROW, LCX_PFS. Ensuite, lorsque la table est supprimée, les métadonnées à son sujet doivent être supprimées de diverses tables système, d'où les 22 entrées de journal de la table système LOP_DELETE_ROWS (Représentées comme ci-dessous)

+----------------------+--------------+-------------------+-------------------+
|        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 complet ci-dessous

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, je pensais que j'essaierais de faire des tests de référence qui ne reposaient sur aucun "cache chaud" afin que, espérons-le, ce soit un test plus réaliste (en utilisant également Postgres, pour voir s'il correspond aux mêmes caractéristiques que les autres réponses publiées) :

Mes benchmarks utilisant postgres 9.3.4 avec une base de données de grande taille, (espérons-le assez grand pour ne pas tenir dans RAM cache):

En utilisant ce script de base de données de test: https://Gist.github.com/rdp/8af84fbb54a430df8fc

avec 10 millions de lignes:

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

avec 100 millions de lignes:

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

Donc, à partir de cela, je présume ce qui suit: drop est "à peu près" aussi rapide (ou plus rapide) que truncate + drop (au moins pour les versions modernes de Postgres), cependant, si vous prévoyez également de vous retourner et de recréer la table, vous pouvez aussi bien coller avec faire un tronçon droit, ce qui est plus rapide qu'une goutte + recréer (c'est logique). FWIW.

note 1: https://stackoverflow.com/questions/11419536/postgresql-truncation-speed/11423886#11423886 (indique que postgres 9.2 peut avoir une troncature plus rapide que les versions précédentes). Comme toujours, comparez avec votre propre système pour voir ses caractéristiques.

note 2: truncate peut être annulé dans postgres, si dans une transaction: http://www.postgresql.org/docs/8.4/static/sql-truncate.html

note 3: tronquer peut, avec de petites tables, parfois être plus lent qu'une suppression: https://stackoverflow.com/questions/11419536/postgresql-truncation-speed/11423886#11423886

2
rogerdpack

Ajout d'une perspective historique ...

La suppression d'une table nécessite la mise à jour de plusieurs tables système, ce qui à son tour nécessite généralement d'apporter ces modifications de table système en une seule transaction (pensez à "commencer le transfert, supprimer les colonnes système, supprimer les objets système, valider").

La "table de dépôt" comprend également la nécessité de désallouer toutes les pages de données/index associées à la table.

Il y a beaucoup, beaucoup, beaucoup d'années ... le processus de désallocation d'espace a été inclus dans la transaction qui a également mis à jour les tables système; le résultat net était que plus le nombre de pages allouées était important, plus il fallait de temps pour désallouer ces pages, plus la transaction (sur les tables système) a été laissée ouverte, et donc une plus grande chance de bloquer (sur les tables système) d'autres processus essayant de créer/supprimer des tables dans tempdb (particulièrement désagréable avec les anciennes pages allpages == verrouillage au niveau de la page et potentiel pour la table escalade de verrouillage de niveau).

L'une des premières méthodes utilisées (à l'époque) pour réduire les conflits sur les tables système consistait à réduire la durée pendant laquelle les verrous étaient maintenus sur les tables système, et un moyen (relativement) facile de le faire était de désallouer les pages de données/index avant de les supprimer. la table.

Tandis que truncate table ne désalloue pas toutes les données/pages d'index, il désalloue toutes les extensions sauf 8 (données); un autre "hack" consistait à supprimer tous les index avant de supprimer la table (ouais, txn séparé sur sysindexes mais un txn plus petit pour drop table).

Quand on considère que (encore une fois, il y a de nombreuses années), il n'y avait qu'une seule base de données 'tempdb', et certaines applications ont fait LOURDE utilisation de ce single ' base de données tempdb, tous les "hacks" susceptibles de réduire les conflits sur les tables système dans "tempdb" étaient avantageux; au fil du temps, les choses se sont améliorées ... plusieurs bases de données temporaires, verrouillage au niveau des lignes sur les tables système, meilleures méthodes de désallocation, etc.

En attendant, l'utilisation du truncate table ne fait rien de mal s'il est laissé dans le code.

1
markp-fuso