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
?
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:
TRUNCATE
n'est pas enregistré, il ne peut donc pas être annulé.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.
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
.
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.
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
rcmodified
pour toutes les colonnes du tableau dans sys.sysrscols
rcrows
dans sysrowsets
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
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
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.