Pourquoi utiliser à la fois TRUNCATE et DROP?


100

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

Beaucoup de mes collègues (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 TABLEdans mes scripts.

Y at-il une bonne raison de faire un TRUNCATEjuste avant un DROP?

Réponses:


130

Non.

TRUNCATEet DROPsont presque identiques en comportement et en rapidité, il est donc inutile de faire un TRUNCATEdroit avant DROP.


Remarque: j'ai écrit cette réponse du point de vue de SQL Server en supposant qu'elle s'appliquerait de la même manière à Sybase. Il semble que ce n’est pas tout à fait le cas .

Remarque: lorsque j'ai posté cette réponse pour la première fois, il y avait plusieurs autres réponses très appréciées - y compris la réponse alors acceptée - qui faisaient plusieurs déclarations fausses telles que: TRUNCATEn'est pas enregistré; TRUNCATEne peut pas être annulé; TRUNCATEest plus rapide que DROP; etc.

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


Quelques faussetés populaires - omniprésentes même parmi les administrateurs de bases de données expérimentés - peuvent avoir motivé cette TRUNCATE-then-DROPtendance. Elles sont:

  • Mythe : TRUNCATEn'est pas connecté, il ne peut donc pas être annulé.
  • Mythe : TRUNCATEest 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 s'appliquer de la même manière à Sybase.

TRUNCATE est enregistré et peut être annulé.

  • TRUNCATEest une opération journalisée, elle peut donc être annulée . Emballez-le simplement 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 cela n’est pas vrai pour Oracle . Bien que journalisé et protégé par la fonctionnalité d'annulation et de restauration d' Oracle, l'utilisateur ne peut pas restaurer les TRUNCATEautres instructions DDL, car Oracle émet des validations implicites immédiatement avant et après toutes les instructions DDL.

  • TRUNCATEest minimalement connecté , par opposition à entièrement connecté. Qu'est-ce que ça veut dire? Dis toi TRUNCATEune table. Au lieu de mettre chaque ligne supprimée dans le journal des transactions, TRUNCATEmarque simplement les pages de données sur lesquelles elles vivent comme non allouées. C'est pourquoi c'est si rapide. C'est également pourquoi vous ne pouvez pas récupérer les lignes d'une TRUNCATEtable avec l'option -ed du journal des transactions à l'aide d'un lecteur de journal. Vous trouverez tout ce qu'il y a en référence aux pages de données désallouées.

    Comparez ceci à DELETE. Si vous avez DELETEtoutes les lignes d'une table et que vous validez la transaction, vous pouvez toujours, en théorie, rechercher les lignes supprimées dans le journal des transactions et les récupérer à partir de là. En effet, DELETEchaque ligne supprimée est écrite dans le journal des transactions. Pour les grandes tables, cela le rendra beaucoup plus lent que TRUNCATE.

DROP est aussi rapide que TRUNCATE.

  • Like TRUNCATE, DROPest une opération à journalisation minimale. Cela signifie DROPpeut être annulé aussi. Cela signifie également que cela fonctionne exactement de la même manière que TRUNCATE. Au lieu de supprimer des lignes individuelles, DROPmarque les pages de données appropriées comme non allouées et marque en outre les métadonnées de la table comme étant supprimées .
  • Parce que, TRUNCATEet DROPfonctionnent exactement de la même manière, ils courent aussi vite les uns que les autres. Il ne sert à rien de TRUNCATEparcourir une table avant de la DROPmodifier. Exécutez ce script de démonstration sur votre instance de développement si vous ne me croyez pas.

    Sur mon ordinateur local avec une mémoire cache chaude, les résultats que je reçois 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 A 134 millions de table de ligne à la fois DROPet TRUNCATEprendre effectivement un rien de temps. (Sur une mémoire cache froide, ils prennent environ 2-3 secondes pour la première fois ou les deux.) Je pense également que la durée moyenne plus élevée de l' opération d' TRUNCATEalors DROPest due aux variations de charge sur ma machine locale et non pas à la combinaison magique de cette combinaison. ordre de grandeur pire que les opérations individuelles. Après tout, ils sont presque exactement la même chose.

    Si vous souhaitez en savoir plus sur les frais d’enregistrement liés à ces opérations, Martin a une explication simple à ce sujet .


52

Tester TRUNCATEensuite DROPet simplement faire DROPdirectement montre que la première approche a en réalité une légère augmentation de la charge de journalisation, de sorte qu'elle peut même être légèrement contre-productive.

L'examen des enregistrements de journaux individuels montre que la TRUNCATE ... DROPversion est presque identique à la DROPversion, à 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                    |
+-----------------+---------------+-------------------------+

La TRUNCATEpremière version finit donc par perdre un peu d’effort à mettre à jour diverses tables système comme suit:

  • Mise rcmodifiedà jour pour toutes les colonnes de la table danssys.sysrscols
  • Mettre rcrowsà jour danssysrowsets
  • Mettre à zéro pgfirst, pgroot, pgfirstiam, pcused, pcdata, pcreservedensys.sysallocunits

Ces lignes de table système ne sont finalement supprimées que lorsque la table est supprimée dans l'instruction suivante.

Une décomposition complète de la journalisation effectuée par TRUNCATEvs DROPest présentée 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 complet par rapport à une table de 1 000 lignes avec une ligne par page. La table consomme au total 1 004 pages en raison de la page d’index racine et de 3 pages d’index de niveau intermédiaire.

8 de ces pages sont des attributions d'une seule page dans des domaines variés, le reste étant réparti sur 125 domaines uniformes. Les 8 désaffectations d'une seule page apparaissent comme les 8 LOP_MODIFY_ROW,LCX_IAMentrées du journal. Les désallocations à 125 degrés en tant que LOP_SET_BITS LCX_GAM,LCX_IAM. Ces deux opérations nécessitent également une mise à jour de la PFSpage associée, d' où les 133 LOP_MODIFY_ROW, LCX_PFSentrées combinées . Ensuite, lorsque la table est réellement supprimée, les métadonnées sur elle doivent être supprimées de diverses tables système, d'où les 22 LOP_DELETE_ROWSentrées du journal de la table système (comptabilisées comme suit).

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

Texte 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

2

OK a pensé que j'essaierais de faire des repères qui ne s'appuient pas sur une «mise en 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 postées) :

Mes benchmarks utilisant postgres 9.3.4 avec une base de données volumineuse (si possible assez volumineux pour ne pas tenir dans le cache RAM):

Utilisation de ce script de base de test: https://gist.github.com/rdp/8af84fbb54a430df8fc0

avec 10 millions de lignes:

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

avec 100M de lignes:

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

Donc, je déduis de ce qui suit: drop est "à peu près" aussi rapide (ou plus rapide) que truncate + drop (du moins pour les versions modernes de Postgres), cependant, si vous envisagez également de retourner et de recréer la table, vous pouvez également: bien s'en tenir à faire une ligne droite tronquée, qui est plus rapide qu'une goutte + recréer (est logique). FWIW.

note 1: https://stackoverflow.com/questions/11419536/postgresql-truncation-speed/11423886#11423886 (indique que postgres 9.2 peut avoir un 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, être parfois plus lent qu'une suppression: https://stackoverflow.com/questions/11419536/postgresql-truncation-speed/11423886#11423886


1

Ajout d'une perspective historique ...

La suppression d’une table nécessite la mise à jour de plusieurs tables système, ce qui nécessite généralement d’apporter ces modifications dans une seule transaction (pensez à "begin tran, delete syscolumns, delete sysobjects, commit").

La «table de dépôt» inclut é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 était inclus dans la transaction qui mettait également à jour les tables système: le résultat net était que plus le nombre de pages allouées était grand, plus il fallait de temps pour désallouer lesdites pages, plus la La transaction (sur les tables système) est restée ouverte, ce qui augmente les chances de bloquer (sur les tables système) les autres processus essayant de créer / supprimer des tables dans tempdb (particulièrement désagréable avec l'ancien allpages == et le potentiel de table - escalade de verrouillage de niveau).

Une des premières méthodes utilisées (il y a bien longtemps) pour réduire les conflits sur les tables système consistait à réduire la durée de blocage des verrous sur les tables système, et un moyen (relativement) simple de le faire était de désallouer les pages de données / index avant de supprimer. la table.

Bien truncate tableque ne désalloue pas toutes les pages de données / index, il désalloue uniquement une étendue de 8 pages (données); un autre 'hack' devait alors supprimer tous les index avant de déposer la table (ouais, séparez txn sur sysindexes mais un plus petit txn pour drop table).

Lorsque vous considérez que (encore, il y a de nombreuses années), il n'y avait qu'une seule base de données 'tempdb', et certaines applications utilisaient LOURDE de cette base de données unique 'tempdb', tous les 'hacks' pouvant réduire le conflit sur les tables système dans "tempdb" ont été bénéfiques; avec le temps, les choses se sont améliorées ... bases de données temporaires multiples, verrouillage des tables système au niveau des lignes, meilleures méthodes de désallocation, etc.

En attendant, l'utilisation de la truncate tablene blesse rien si elle est laissée dans le code.


-2

Il est judicieux de faire TRUNCATE pour les tables qui ont des clés étrangères. Cependant, pour les tables temporaires, DROP suffit


TRUNCATE éviterait-il en quelque sorte un conflit de clé étrangère? Comment?
user259412

1

-8

Le but de truncateest de supprimer simplement et irrévocablement tout ce qui se trouve dans le tableau (certaines spécificités techniques basées sur les moteurs de magasin de données peuvent légèrement différer) - saut de la journalisation lourde, etc.

drop tableenregistre toutes les modifications au fur et à mesure que les modifications sont apportées. Donc, pour minimiser la journalisation et réduire les pertes de système inutiles, je soupçonne qu'une très grande table pourrait être tronquée en premier, puis abandonnée.

truncate peut être encapsulé dans une transaction (ce qui serait l'option la plus sûre) qui, bien sûr, vous permettra d'annuler l'opération.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.