INSERT efficace dans une table avec un index clusterisé


28

J'ai une instruction SQL qui insère des lignes dans une table avec un index cluster sur la colonne TRACKING_NUMBER.

PAR EXEMPLE:

INSERT INTO TABL_NAME (TRACKING_NUMBER, COLB, COLC) 
SELECT TRACKING_NUMBER, COL_B, COL_C 
FROM STAGING_TABLE

Ma question est la suivante: est-il utile d'utiliser une clause ORDER BY dans l'instruction SELECT pour la colonne d'index cluster, ou un gain obtenu serait-il annulé par le tri supplémentaire requis pour la clause ORDER BY?

Réponses:


18

Comme les autres réponses l'indiquent déjà, SQL Server peut ou non garantir explicitement que les lignes sont triées dans un ordre d'index cluster avant le insert.

Cela dépend du fait que l'opérateur d'index cluster dans le plan possède ou non le DMLRequestSortjeu de propriétés (qui à son tour dépend du nombre estimé de lignes insérées).

Si vous constatez que SQL Server sous-estime cela pour une raison quelconque, vous pourriez bénéficier de l'ajout d'un explicite ORDER BYà la SELECTrequête pour minimiser les fractionnements de page et la fragmentation consécutive de l' INSERTopération.

Exemple:

use tempdb;

GO

CREATE TABLE T(N INT PRIMARY KEY,Filler char(2000))

CREATE TABLE T2(N INT PRIMARY KEY,Filler char(2000))

GO

DECLARE @T TABLE (U UNIQUEIDENTIFIER PRIMARY KEY DEFAULT NEWID(),N int)

INSERT INTO @T(N)
SELECT number 
FROM master..spt_values
WHERE type = 'P' AND number BETWEEN 0 AND 499

/*Estimated row count wrong as inserting from table variable*/
INSERT INTO T(N)
SELECT T1.N*1000 + T2.N
FROM @T T1, @T T2

/*Same operation using explicit sort*/    
INSERT INTO T2(N)
SELECT T1.N*1000 + T2.N
FROM @T T1, @T T2
ORDER BY T1.N*1000 + T2.N


SELECT avg_fragmentation_in_percent,
       fragment_count,
       page_count,
       avg_page_space_used_in_percent,
       record_count
FROM   sys.dm_db_index_physical_stats(2, OBJECT_ID('T'), NULL, NULL, 'DETAILED')
;  


SELECT avg_fragmentation_in_percent,
       fragment_count,
       page_count,
       avg_page_space_used_in_percent,
       record_count
FROM   sys.dm_db_index_physical_stats(2, OBJECT_ID('T2'), NULL, NULL, 'DETAILED')
;  

Des spectacles Tmassivement fragmentés

avg_fragmentation_in_percent fragment_count       page_count           avg_page_space_used_in_percent record_count
---------------------------- -------------------- -------------------- ------------------------------ --------------------
99.3116118225536             92535                92535                67.1668272794663               250000
99.5                         200                  200                  74.2868173956017               92535
0                            1                    1                    32.0978502594514               200

Mais pour la T2fragmentation est minime

avg_fragmentation_in_percent fragment_count       page_count           avg_page_space_used_in_percent record_count
---------------------------- -------------------- -------------------- ------------------------------ --------------------
0.376                        262                  62500                99.456387447492                250000
2.1551724137931              232                  232                  43.2438349394613               62500
0                            1                    1                    37.2374598468001               232

Inversement, il peut arriver que vous souhaitiez forcer SQL Server à sous-estimer le nombre de lignes lorsque vous savez que les données sont déjà pré-triées et que vous souhaitez éviter un tri inutile. Un exemple notable est lors de l'insertion d'un grand nombre de lignes dans une table avec une newsequentialidclé d'index cluster. Dans les versions de SQL Server antérieures à Denali, SQL Server ajoute une opération de tri inutile et potentiellement coûteuse . Cela peut être évité en

DECLARE @var INT =2147483647

INSERT INTO Foo
SELECT TOP (@var) *
FROM Bar

SQL Server estimera alors que 100 lignes seront insérées, quelle que soit la taille en Bardessous du seuil auquel un tri est ajouté au plan. Cependant, comme indiqué dans les commentaires ci-dessous, cela signifie que l'insert ne pourra malheureusement pas tirer parti d'une journalisation minimale.



12

Si l'optimiseur décide qu'il serait plus efficace de trier les données avant l'insertion, il le fera quelque part en amont de l'opérateur d'insertion. Si vous introduisez un tri dans le cadre de votre requête, l'optimiseur doit se rendre compte que les données sont déjà triées et ne plus recommencer. Notez que le plan d'exécution choisi peut varier d'une exécution à l'autre en fonction du nombre de lignes insérées à partir de votre table intermédiaire.

Si vous pouvez capturer les plans d'exécution du processus avec et sans le tri explicite, joignez-les à votre question pour commentaire.

Éditer: 2011-10-28 17:00

@ La réponse de Gonsalu semble montrer qu'une opération de tri se produit toujours, ce n'est pas le cas. Scripts de démonstration requis!

Comme les scripts devenaient assez volumineux, je les ai déplacés vers Gist . Pour faciliter l'expérimentation, les scripts utilisent le mode SQLCMD. Les tests s'exécutent sur 2K5SP3, double cœur, 8 Go.

Les tests d'insertion couvrent trois scénarios:

  1. Index clusterisé des données intermédiaires dans le même ordre que la cible.
  2. Index clusterisé des données intermédiaires dans l'ordre inverse.
  3. Données intermédiaires regroupées par col2 qui contient un INT aléatoire.

Première course, en insérant 25 lignes.

1ère manche, 25 rangées

Les trois plans d'exécution sont identiques, aucun tri ne se produit nulle part dans le plan et l'analyse d'index cluster est "ordonnée = fausse".

Deuxième passage, en insérant 26 lignes.

2e manche, 26 rangs

Cette fois, les plans diffèrent.

  • Le premier montre l'analyse d'index clusterisé comme ordonné = faux. Aucun tri ne s'est produit car les données source sont correctement triées.
  • Dans le second, l'analyse d'index clusterisé comme ordonné = vrai, en arrière. Nous n'avons donc pas d'opération de tri, mais la nécessité de trier les données est reconnue par l'optimiseur et il scanne dans l'ordre inverse.
  • Le troisième montre un opérateur de tri.

Il y a donc un point de basculement où l'optimiseur estime qu'une sorte est nécessaire. Comme le montre @MartinSmith, cela semble être basé sur les lignes estimées à insérer. Sur mon banc d'essai, 25 ne nécessitent pas de tri, 26 le font (2K5SP3, double cœur, 8 Go)

Le script SQLCMD inclut des variables qui permettent à la taille des lignes de la table de changer (en modifiant la densité de page) et le nombre de lignes dans dbo.MyTable avant les insertions supplémentaires. D'après mes tests, aucun n'a d'effet sur le point de basculement.

Si certains lecteurs sont si enclins, veuillez exécuter les scripts et ajouter votre point de basculement en tant que commentaire. Intéressé pour savoir si cela varie selon les bancs d'essai et / ou les versions.

Éditer: 2011-10-28 20:15

Tests répétés sur la même plate-forme mais avec 2K8R2. Cette fois, le point de basculement est de 251 lignes. Encore une fois, la variation de la densité des pages et du nombre de lignes existantes n'a aucun effet.


8

La ORDER BYclause de la SELECTdéclaration est redondante.

Il est redondant car les lignes qui seront insérées, si elles doivent être triées , sont quand même triées.

Créons un cas de test.

CREATE TABLE #Test (
    id INTEGER NOT NULL
);

CREATE UNIQUE CLUSTERED INDEX CL_Test_ID ON #Test (id);

CREATE TABLE #Sequence (
    number INTEGER NOT NULL
);

INSERT INTO #Sequence
SELECT number FROM master..spt_values WHERE name IS NULL;

Activons l'affichage du texte des plans de requête réels, afin que nous puissions voir quelles tâches sont effectuées par le processeur de requêtes.

SET STATISTICS PROFILE ON;
GO

Maintenant, plaçons INSERT2K lignes dans la table sans ORDER BYclause.

INSERT INTO #Test
SELECT number
  FROM #Sequence

Le plan d'exécution réel de cette requête est le suivant.

INSERT INTO #Test  SELECT number    FROM #Sequence
  |--Clustered Index Insert(OBJECT:([tempdb].[dbo].[#Test]), SET:([tempdb].[dbo].[#Test].[id] = [tempdb].[dbo].[#Sequence].[number]))
       |--Top(ROWCOUNT est 0)
            |--Sort(ORDER BY:([tempdb].[dbo].[#Sequence].[number] ASC))
                 |--Table Scan(OBJECT:([tempdb].[dbo].[#Sequence]))

Comme vous pouvez le voir, il y a un opérateur de tri avant que l'INSERT réel ne se produise.

Maintenant, effaçons le tableau et INSERT2k lignes dans le tableau avec la ORDER BYclause.

TRUNCATE TABLE #Test;
GO

INSERT INTO #Test
SELECT number
  FROM #Sequence
 ORDER BY number

Le plan d'exécution réel de cette requête est le suivant.

INSERT INTO #Test  SELECT number    FROM #Sequence   ORDER BY number
  |--Clustered Index Insert(OBJECT:([tempdb].[dbo].[#Test]), SET:([tempdb].[dbo].[#Test].[id] = [tempdb].[dbo].[#Sequence].[number]))
       |--Top(ROWCOUNT est 0)
            |--Sort(ORDER BY:([tempdb].[dbo].[#Sequence].[number] ASC))
                 |--Table Scan(OBJECT:([tempdb].[dbo].[#Sequence]))

Notez qu'il s'agit du même plan d'exécution que celui utilisé pour l' INSERTinstruction sans la ORDER BYclause.

Maintenant, l' Sortopération n'est pas toujours requise, comme Mark Smith l'a montré dans une autre réponse (si le nombre de lignes à insérer est faible), mais la ORDER BYclause est toujours redondante dans ce cas, car même avec une explicite ORDER BY, aucune Sortopération n'est générée par le processeur de requêtes.

Vous pouvez optimiser une INSERTinstruction dans une table avec un index cluster, en utilisant un journal minimalement connecté INSERT, mais cela est hors de portée pour cette question.

Mise à jour le 2011-11-02: comme l'a montré Mark Smith , les INSERTs dans une table avec un index clusterisé ne nécessitent pas toujours d'être triés - la ORDER BYclause est également redondante dans ce cas, cependant.

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.