Pourquoi DELETE laisse-t-il un effet persistant sur les performances?

20

À la fin se trouve un script de test pour comparer les performances entre une variable @table et une table #temp. Je pense que je l'ai configuré correctement - les temporisations des performances sont prises en dehors des commandes DELETE / TRUNCATE. Les résultats que j'obtiens sont les suivants (temps en millisecondes).

@Table Variable  #Temp (delete)  #Temp (truncate)
---------------  --------------  ----------------
5723             5180            5506
15636            14746           7800
14506            14300           5583
14030            15460           5386
16706            16186           5360

Juste pour m'assurer que je suis sain d'esprit, cela montre que CURRENT_TIMESTAMP (aka GetDate()) est pris au moment de l'instruction, pas du lot, donc il ne devrait pas y avoir d'interaction entre TRUNCATE / DELETE avec l' SET @StartTime = CURRENT_TIMESTAMPinstruction.

select current_timestamp
waitfor delay '00:00:04'
select current_timestamp

-----------------------
2012-10-21 11:29:20.290

-----------------------
2012-10-21 11:29:24.290

Il est tout à fait cohérent dans le saut entre la première manche et les manches suivantes lorsque DELETE est utilisé pour effacer la table. Qu'est-ce qui me manque dans ma compréhension de DELETE ? J'ai répété cela plusieurs fois, échangé l'ordre, dimensionné tempdb pour ne pas nécessiter de croissance, etc.

CREATE TABLE #values (
  id int identity primary key, -- will be clustered
  name varchar(100) null,
  number int null,
  type char(3) not null,
  low int null,
  high int null,
  status smallint not null
);
GO
SET NOCOUNT ON;

DECLARE @values TABLE (
  id int identity primary key clustered,
  name varchar(100) null,
  number int null,
  type char(3) not null,
  low int null,
  high int null,
  status smallint not null
);
DECLARE  @ExecutionTime  TABLE(      Duration bigINT    ) 
DECLARE  @StartTime DATETIME,  @i INT = 1; 
WHILE (@i <= 5) 
  BEGIN 
    DELETE @values;
    DBCC freeproccache With NO_InfoMSGS;
    DBCC DROPCLEANBUFFERS With NO_InfoMSGS;
    SET @StartTime = CURRENT_TIMESTAMP -- alternate getdate() 
    /****************** measured process ***********************/ 

    INSERT @values SELECT a.* FROM master..spt_values a join master..spt_values b on b.type='P' and b.number < 1000;

    /**************** end measured process *********************/ 
    INSERT @ExecutionTime 
    SELECT DurationInMilliseconds = datediff(ms,@StartTime,CURRENT_TIMESTAMP) 
    SET @i +=  1 
  END -- WHILE 

SELECT DurationInMilliseconds = Duration FROM   @ExecutionTime 
GO 

-- Temporary table
DECLARE  @ExecutionTime  TABLE(      Duration bigINT    ) 
DECLARE  @StartTime DATETIME,  @i INT = 1; 
WHILE (@i <= 5) 
  BEGIN 
    delete #values;
    -- TRUNCATE TABLE #values;
    DBCC freeproccache With NO_InfoMSGS;
    DBCC DROPCLEANBUFFERS With NO_InfoMSGS;
    SET @StartTime = CURRENT_TIMESTAMP -- alternate getdate() 
    /****************** measured process ***********************/ 

    INSERT #values SELECT a.* FROM master..spt_values a join master..spt_values b on b.type='P' and b.number < 1000;

    /**************** end measured process *********************/ 
    INSERT @ExecutionTime 
    SELECT DurationInMilliseconds = datediff(ms,@StartTime,CURRENT_TIMESTAMP) 
    SET @i +=  1 
  END -- WHILE 

SELECT DurationInMilliseconds = Duration FROM   @ExecutionTime 
GO

DROP TABLE  #values 
SET NOCOUNT OFF;
孔夫子
la source

Réponses:

20

Cette différence ne semble s'appliquer que lorsque l'objet est un arbre B +. Lors de la suppression primary keyde la variable sur la table, il s'agit donc d'un tas, j'ai obtenu les résultats suivants

2560
2120
2080
2130
2140

Mais avec le PK, j'ai également trouvé un schéma similaire dans mes tests avec des résultats typiques ci-dessous.

+--------+--------+---------+-------------------+
| @table | #table | ##table | [permanent_table] |
+--------+--------+---------+-------------------+
|   2670 |   2683 |    9603 |              9703 |
|   6823 |   6840 |    9723 |              9790 |
|   6813 |   6816 |    9626 |              9703 |
|   6883 |   6816 |    9600 |              9716 |
|   6840 |   6856 |    9610 |              9673 |
+--------+--------+---------+-------------------+

Ma théorie est qu'il existe une certaine optimisation disponible lors de l'insertion en bloc dans les arborescences B + temporaires locales qui ne s'applique que si aucune page n'est déjà allouée.

Je fonde cela sur les observations suivantes.

  1. Lors de l'exécution de différentes versions de votre code de test, je n'ai vu ce modèle qu'avec les tables @table_variableset #temp. Pas de tables permanentes tempdbni de ##tables.

  2. Pour obtenir des performances plus lentes, il n'est pas nécessaire d'avoir précédemment ajouté et supprimé une grande quantité de lignes du tableau. Il suffit d'ajouter une seule ligne et de la laisser dedans.

  3. TRUNCATEdésalloue toutes les pages du tableau. DELETEn'entraîne pas la désallocation de la dernière page du tableau.

  4. L'utilisation du profileur VS 2012 montre que dans le cas le plus rapide, SQL Server utilise un chemin de code différent. 36% du temps est consacré à sqlmin.dll!RowsetBulk::InsertRow61% du temps consacré au sqlmin.dll!RowsetNewSS::InsertRowcas le plus lent.

Fonctionnement

SELECT * 
FROM sys.dm_db_index_physical_stats(2,OBJECT_ID('tempdb..#values'),1,NULL, 'DETAILED')

après le retour de la suppression

+-------------+------------+--------------+--------------------+
| index_level | page_count | record_count | ghost_record_count |
+-------------+------------+--------------+--------------------+
|           0 |          1 |            0 |                  1 |
|           1 |          1 |            1 |                  0 |
|           2 |          1 |            1 |                  0 |
+-------------+------------+--------------+--------------------+

J'ai trouvé qu'il était possible de réduire quelque peu l'écart temporel en activant l'indicateur de trace 610 .

Cela a eu pour effet de réduire la quantité de l' exploitation forestière essentiellement pour les insertions suivantes (vers le bas de 350 Mo à 103 Mo , car il ne consigne plus les valeurs des lignes individuelles insérées) , mais ce qui a eu qu'une amélioration mineure dans timings pour les 2e et suivants @table, les #tablecas et l'écart demeure. L'indicateur de trace a considérablement amélioré les performances générales des insertions vers les deux autres types de table.

+--------+--------+---------+-------------------+
| @table | #table | ##table | [permanent_table] |
+--------+--------+---------+-------------------+
|   2663 |   2670 |    5403 |              5426 |
|   5390 |   5396 |    5410 |              5403 |
|   5373 |   5390 |    5410 |              5403 |
|   5393 |   5410 |    5406 |              5433 |
|   5386 |   5396 |    5390 |              5420 |
+--------+--------+---------+-------------------+

En regardant dans le journal des transactions, j'ai remarqué que les insertions initiales contre les tables temporaires locales vides semblent encore plus minimalement enregistrées (à 96 Mo).

Notamment, ces insertions plus rapides n'avaient que des 657transactions ( LOP_BEGIN_XACT/ LOP_COMMIT_XACTpaires) par rapport à plus 10,000dans les cas plus lents. En particulier, les LOP_FORMAT_PAGEopérations semblent beaucoup réduites. Les cas plus lents ont une entrée de journal des transactions pour cela pour chaque page du tableau (environ 10,270) par rapport à seulement 4ces entrées dans le cas rapide.

Le journal utilisé dans les trois cas était le suivant (j'ai supprimé les enregistrements du journal pour les mises à jour des tables de base du système afin de réduire la quantité de texte mais ils sont toujours inclus dans les totaux)

Enregistrement de la première insertion contre @table_var(96,5 Mo)

+-----------------------+----------+----------------------------------------------+---------------+---------+
|       Operation       | Context  |                AllocUnitName                 | Size in Bytes |   Cnt   |
+-----------------------+----------+----------------------------------------------+---------------+---------+
| LOP_BEGIN_XACT        | LCX_NULL | NULL                                         |         83876 |     658 |
| LOP_COMMIT_XACT       | LCX_NULL | NULL                                         |         34164 |     657 |
| LOP_CREATE_ALLOCCHAIN | LCX_NULL | NULL                                         |           120 |       3 |
| LOP_FORMAT_PAGE       | LCX_HEAP | dbo.#531856C7                                |            84 |       1 |
| LOP_FORMAT_PAGE       | LCX_IAM  | dbo.#4F47C5E3.PK__#4F47C5E__3213E83F51300E55 |            84 |       1 |
| LOP_FORMAT_PAGE       | LCX_IAM  | dbo.#531856C7                                |            84 |       1 |
| LOP_FORMAT_PAGE       | LCX_IAM  | Unknown Alloc Unit                           |            84 |       1 |
| LOP_HOBT_DDL          | LCX_NULL | NULL                                         |           216 |       6 |
| LOP_HOBT_DELTA        | LCX_NULL | NULL                                         |           320 |       5 |
| LOP_IDENT_NEWVAL      | LCX_NULL | NULL                                         |     100240000 | 2506000 |
| LOP_INSERT_ROWS       | LCX_HEAP | dbo.#531856C7                                |            72 |       1 |
| LOP_MODIFY_ROW        | LCX_IAM  | dbo.#531856C7                                |            88 |       1 |
| LOP_MODIFY_ROW        | LCX_PFS  | dbo.#4F47C5E3.PK__#4F47C5E__3213E83F51300E55 |        158592 |    1848 |
| LOP_MODIFY_ROW        | LCX_PFS  | dbo.#531856C7                                |            80 |       1 |
| LOP_MODIFY_ROW        | LCX_PFS  | Unknown Alloc Unit                           |        216016 |    2455 |
| LOP_SET_BITS          | LCX_GAM  | dbo.#4F47C5E3.PK__#4F47C5E__3213E83F51300E55 |         84360 |    1406 |
| LOP_SET_BITS          | LCX_GAM  | Unknown Alloc Unit                           |        147120 |    2452 |
| LOP_SET_BITS          | LCX_IAM  | dbo.#4F47C5E3.PK__#4F47C5E__3213E83F51300E55 |         84360 |    1406 |
| LOP_SET_BITS          | LCX_IAM  | Unknown Alloc Unit                           |        147120 |    2452 |
| Total                 | NULL     | NULL                                         |     101209792 | 2519475 |
+-----------------------+----------+----------------------------------------------+---------------+---------+

Déconnexion des insertions suivantes TF 610 (350 Mo)

+-----------------------+--------------------+----------------------------------------------+---------------+---------+
|       Operation       |      Context       |                AllocUnitName                 | Size in Bytes |   Cnt   |
+-----------------------+--------------------+----------------------------------------------+---------------+---------+
| LOP_BEGIN_CKPT        | LCX_NULL           | NULL                                         |            96 |       1 |
| LOP_BEGIN_XACT        | LCX_NULL           | NULL                                         |       1520696 |   12521 |
| LOP_COMMIT_XACT       | LCX_NULL           | NULL                                         |        651040 |   12520 |
| LOP_CREATE_ALLOCCHAIN | LCX_NULL           | NULL                                         |            40 |       1 |
| LOP_DELETE_SPLIT      | LCX_INDEX_INTERIOR | dbo.#4F47C5E3.PK__#4F47C5E__3213E83F51300E55 |          2160 |      36 |
| LOP_END_CKPT          | LCX_NULL           | NULL                                         |           136 |       1 |
| LOP_FORMAT_PAGE       | LCX_HEAP           | dbo.#4F47C5E3.PK__#4F47C5E__3213E83F51300E55 |        859236 |   10229 |
| LOP_FORMAT_PAGE       | LCX_IAM            | Unknown Alloc Unit                           |            84 |       1 |
| LOP_FORMAT_PAGE       | LCX_INDEX_INTERIOR | dbo.#4F47C5E3.PK__#4F47C5E__3213E83F51300E55 |          3108 |      37 |
| LOP_HOBT_DDL          | LCX_NULL           | NULL                                         |           648 |      18 |
| LOP_HOBT_DELTA        | LCX_NULL           | NULL                                         |        657088 |   10267 |
| LOP_IDENT_NEWVAL      | LCX_NULL           | NULL                                         |     100239960 | 2505999 |
| LOP_INSERT_ROWS       | LCX_CLUSTERED      | dbo.#4F47C5E3.PK__#4F47C5E__3213E83F51300E55 |     258628000 | 2506000 |
| LOP_INSERT_ROWS       | LCX_HEAP           | dbo.#531856C7                                |            72 |       1 |
| LOP_INSERT_ROWS       | LCX_INDEX_INTERIOR | dbo.#4F47C5E3.PK__#4F47C5E__3213E83F51300E55 |       1042776 |   10302 |
| LOP_MODIFY_HEADER     | LCX_HEAP           | dbo.#4F47C5E3.PK__#4F47C5E__3213E83F51300E55 |        859236 |   10229 |
| LOP_MODIFY_HEADER     | LCX_INDEX_INTERIOR | dbo.#4F47C5E3.PK__#4F47C5E__3213E83F51300E55 |          3192 |      38 |
| LOP_MODIFY_ROW        | LCX_IAM            | dbo.#4F47C5E3.PK__#4F47C5E__3213E83F51300E55 |           704 |       8 |
| LOP_MODIFY_ROW        | LCX_PFS            | dbo.#4F47C5E3.PK__#4F47C5E__3213E83F51300E55 |        934264 |   11550 |
| LOP_MODIFY_ROW        | LCX_PFS            | Unknown Alloc Unit                           |        783984 |    8909 |
| LOP_SET_BITS          | LCX_GAM            | dbo.#4F47C5E3.PK__#4F47C5E__3213E83F51300E55 |         76980 |    1283 |
| LOP_SET_BITS          | LCX_GAM            | Unknown Alloc Unit                           |        534480 |    8908 |
| LOP_SET_BITS          | LCX_IAM            | dbo.#4F47C5E3.PK__#4F47C5E__3213E83F51300E55 |         76980 |    1283 |
| LOP_SET_BITS          | LCX_IAM            | Unknown Alloc Unit                           |        534480 |    8908 |
| LOP_SHRINK_NOOP       | LCX_NULL           | NULL                                         |            32 |       1 |
| LOP_XACT_CKPT         | LCX_NULL           | NULL                                         |            92 |       1 |
| Total                 | NULL               | NULL                                         |     367438748 | 5119297 |
+-----------------------+--------------------+----------------------------------------------+---------------+---------+

Enregistrement des insertions suivantes TF 610 (103 Mo)

+-------------------------+-------------------------+----------------------------------------------+---------------+---------+
|        Operation        |         Context         |                AllocUnitName                 | Size in Bytes |   Cnt   |
+-------------------------+-------------------------+----------------------------------------------+---------------+---------+
| LOP_BEGIN_CKPT          | LCX_NULL                | NULL                                         |           192 |       2 |
| LOP_BEGIN_XACT          | LCX_NULL                | NULL                                         |       1339796 |   11099 |
| LOP_BULK_EXT_ALLOCATION | LCX_NULL                | NULL                                         |         20616 |     162 |
| LOP_COMMIT_XACT         | LCX_NULL                | NULL                                         |        577096 |   11098 |
| LOP_CREATE_ALLOCCHAIN   | LCX_NULL                | NULL                                         |            40 |       1 |
| LOP_DELETE_SPLIT        | LCX_INDEX_INTERIOR      | dbo.#6DCC4D03.PK__#6DCC4D0__3213E83F6FB49575 |          2160 |      36 |
| LOP_END_CKPT            | LCX_NULL                | NULL                                         |           272 |       2 |
| LOP_FORMAT_PAGE         | LCX_BULK_OPERATION_PAGE | dbo.#6DCC4D03.PK__#6DCC4D0__3213E83F6FB49575 |        863520 |   10280 |
| LOP_FORMAT_PAGE         | LCX_IAM                 | Unknown Alloc Unit                           |            84 |       1 |
| LOP_FORMAT_PAGE         | LCX_INDEX_INTERIOR      | dbo.#6DCC4D03.PK__#6DCC4D0__3213E83F6FB49575 |          3108 |      37 |
| LOP_HOBT_DELTA          | LCX_NULL                | NULL                                         |        666496 |   10414 |
| LOP_IDENT_NEWVAL        | LCX_NULL                | NULL                                         |     100239960 | 2505999 |
| LOP_INSERT_ROWS         | LCX_CLUSTERED           | dbo.#6DCC4D03.PK__#6DCC4D0__3213E83F6FB49575 |         23544 |     218 |
| LOP_INSERT_ROWS         | LCX_HEAP                | dbo.#719CDDE7                                |            72 |       1 |
| LOP_INSERT_ROWS         | LCX_INDEX_INTERIOR      | dbo.#6DCC4D03.PK__#6DCC4D0__3213E83F6FB49575 |       1042776 |   10302 |
| LOP_MODIFY_HEADER       | LCX_BULK_OPERATION_PAGE | dbo.#6DCC4D03.PK__#6DCC4D0__3213E83F6FB49575 |        780216 |   10266 |
| LOP_MODIFY_HEADER       | LCX_HEAP                | dbo.#6DCC4D03.PK__#6DCC4D0__3213E83F6FB49575 |       1718472 |   20458 |
| LOP_MODIFY_HEADER       | LCX_INDEX_INTERIOR      | dbo.#6DCC4D03.PK__#6DCC4D0__3213E83F6FB49575 |          3192 |      38 |
| LOP_MODIFY_ROW          | LCX_IAM                 | dbo.#6DCC4D03.PK__#6DCC4D0__3213E83F6FB49575 |           704 |       8 |
| LOP_MODIFY_ROW          | LCX_PFS                 | dbo.#6DCC4D03.PK__#6DCC4D0__3213E83F6FB49575 |        114832 |    1307 |
| LOP_MODIFY_ROW          | LCX_PFS                 | Unknown Alloc Unit                           |        231696 |    2633 |
| LOP_RANGE_INSERT        | LCX_NULL                | NULL                                         |            48 |       1 |
| LOP_SET_BITS            | LCX_GAM                 | dbo.#6DCC4D03.PK__#6DCC4D0__3213E83F6FB49575 |         77100 |    1285 |
| LOP_SET_BITS            | LCX_GAM                 | Unknown Alloc Unit                           |        157920 |    2632 |
| LOP_SET_BITS            | LCX_IAM                 | dbo.#6DCC4D03.PK__#6DCC4D0__3213E83F6FB49575 |         77100 |    1285 |
| LOP_SET_BITS            | LCX_IAM                 | Unknown Alloc Unit                           |        157920 |    2632 |
| LOP_XACT_CKPT           | LCX_NULL                | NULL                                         |            92 |       1 |
| Total                   | NULL                    | NULL                                         |     108102960 | 2602218 |
+-------------------------+-------------------------+----------------------------------------------+---------------+---------+
Martin Smith
la source
Merci pour la confirmation détaillée. Donc, la question se pose toujours, pourquoi DELETE ne retourne pas la table à vraiment vide, en utilisant votre terme. En outre, cela justifierait l'utilisation de tables #temp si clear / populate est utilisé dans une boucle de traitement par lots.
1
@RichardTheKiwi - L'avantage de TRUNCATEplus DELETEà lui seul plaiderait également pour cela. Je considérerais également rarement les variables de table pour un grand nombre de lignes.
Martin Smith
Cela peut sembler paresseux, mais la répétition d'une insertion de 1 à 10 enregistrements (variable) 1000 fois dans un lot ne présenterait-elle pas les mêmes symptômes? L'utilisation d'un grand nombre de lignes ne fait qu'aggraver le problème et fournir une échelle pour mieux voir la différence. L'essentiel de la question est de prouver d'une manière ou d'une autre que les tables #temp seraient mieux, une fois que nous savons quelle est la différence.
Eh bien, ma théorie est que c'est l'allocation des 10,000+pages qui se fait de manière beaucoup plus optimisée et semble en éviter une surcharge par page. Pour les inserts plus petits, je m'attendrais à ce qu'une telle différence soit moins significative.
Martin Smith
@RichardTheKiwi - Merci! Il y a probablement plus à dire à ce sujet. Comme je vais essayer de passer à la même version que SQL Kiwi et de voir si je vois toujours les différents chemins de code. Si c'est le cas, c'est peut-être dépendant du matériel que cela fait une telle différence (mes tests ont été sur mon ordinateur de bureau avec toutes les données et les fichiers journaux sur le même SSD)
Martin Smith
0

Observation et spéculation. . .

Sur certains systèmes, CURRENT_TIMESTAMP est défini comme l'heure au début de la transaction en cours. Une recherche rapide n'a révélé aucune documentation définitive sur le comportement de CURRENT_TIMESTAMP sur SQL Server. Mais le mode par défaut de SQL Server est de valider automatiquement les transactions, et il n'y a pas de BEGIN TRANSACTION ici, donc cela devrait être l'heure immédiatement avant l'instruction INSERT. (L'instruction DELETE doit automatiquement être validée et, quelle que soit la façon dont CURRENT_TIMESTAMP fonctionne sur SQL Server, elle ne doit avoir rien à voir avec l'instruction DELETE lorsque vous utilisez des transactions automatiquement validées.)

Lors de la première itération, l'instruction DELETE n'a pas vraiment de travail à faire et il n'y a aucune ligne individuelle à consigner. L'optimiseur le sait peut-être, ce qui réduit le temps de la première itération. (La combinaison d'aucune ligne à supprimer et d'aucune ligne individuelle à consigner.)

Vous pouvez tester cela (je pense) en l'insérant avant de le supprimer.

Mike Sherrill 'Cat Recall'
la source
Je vais cesser de répondre aux questions aujourd'hui. Ou quoi que ce soit que je fais quand je tape des choses dans cette case.
Mike Sherrill 'Cat Recall'
Cette réponse doit-elle être supprimée comme obsolète, tangentielle et distrayante?