Pourquoi la variable de table force-t-elle un balayage d'index alors que la table temporaire utilise la recherche et la recherche de signets?


18

J'essaie de comprendre pourquoi l'utilisation d'une variable de table empêche l'optimiseur d'utiliser une recherche d'index puis une recherche de signet par rapport à une analyse d'index.

Remplir la table:

CREATE TABLE dbo.Test 
(
    RowKey INT NOT NULL PRIMARY KEY, 
    SecondColumn CHAR(1) NOT NULL DEFAULT 'x',
    ForeignKey INT NOT NULL 
) 

INSERT dbo.Test 
(
    RowKey, 
    ForeignKey
) 
SELECT TOP 1000000 
    ROW_NUMBER() OVER (ORDER BY (SELECT 0)),
    ABS(CHECKSUM(NEWID()) % 10)     
FROM sys.all_objects s1
CROSS JOIN sys.all_objects s2 

CREATE INDEX ix_Test_1 ON dbo.Test (ForeignKey) 

Remplissez une variable de table avec un seul enregistrement et essayez de rechercher la clé primaire et la deuxième colonne en recherchant dans la colonne de clé étrangère:

DECLARE @Keys TABLE (RowKey INT NOT NULL) 

INSERT @Keys (RowKey) VALUES (10)

SELECT 
    t.RowKey,
    t.SecondColumn
FROM
    dbo.Test t 
INNER JOIN 
    @Keys k
ON
    t.ForeignKey = k.RowKey

Voici le plan d'exécution:

entrez la description de l'image ici

Maintenant, la même requête utilisant une table temporaire à la place:

CREATE TABLE #Keys (RowKey INT NOT NULL) 

INSERT #Keys (RowKey) VALUES (10) 

SELECT 
    t.RowKey,
    t.SecondColumn
FROM
    dbo.Test t 
INNER JOIN 
    #Keys k
ON
    t.ForeignKey = k.RowKey

Ce plan de requête utilise une recherche et recherche de signets:

entrez la description de l'image ici

Pourquoi l'optimiseur souhaite-t-il effectuer la recherche de signet avec la table temporaire, mais pas la variable de table?

La variable de table est utilisée dans cet exemple pour représenter les données provenant d'un type de table défini par l'utilisateur dans une procédure stockée.

Je me rends compte que la recherche d'indice pourrait ne pas être appropriée si la valeur de la clé étrangère s'est produite des centaines de milliers de fois. Dans ce cas, une analyse serait probablement un meilleur choix. Pour le scénario que j'ai créé, il n'y avait pas de ligne avec une valeur de 10. Je pense toujours que le comportement est intéressant et j'aimerais savoir s'il y a une raison à cela.

SQL Fiddle

L'ajout OPTION (RECOMPILE)n'a pas changé le comportement. L'UDDT possède une clé primaire.

@@VERSION est SQL Server 2008 R2 (SP2) - 10.50.4042.0 (X64) (Build 7601: Service Pack 1) (Hyperviseur)

Réponses:


15

La raison de ce comportement est que SQL Server ne peut pas déterminer combien de lignes correspondront à ForeignKey, car il n'y a pas d'index avec RowKey comme colonne de tête (il peut le déduire des statistiques sur la table #temp, mais celles-ci ne le font pas existent pour les variables de table / UDTT), donc il fait une estimation de 100 000 lignes, ce qui est mieux géré avec un scan qu'une recherche + recherche. Au moment où SQL Server se rend compte qu'il n'y a qu'une seule ligne, il est trop tard.

Vous pourriez être en mesure de construire votre UDTT différemment; dans les versions plus récentes de SQL Server, vous pouvez créer des index secondaires sur les variables de table, mais cette syntaxe n'est pas disponible dans 2008 R2.

BTW, vous pouvez obtenir le comportement de recherche (au moins dans mes essais limités) si vous essayez d'éviter le bitmap / sonde en suggérant une jointure de boucles imbriquées:

DECLARE @Keys TABLE (RowKey INT PRIMARY KEY); -- can't hurt

INSERT @Keys (RowKey) VALUES (10);

SELECT 
     t.RowKey
    ,t.SecondColumn
FROM
    dbo.Test t 
INNER JOIN 
    @Keys k
ON
    t.ForeignKey = k.RowKey
    OPTION (LOOP JOIN);

J'ai appris cette astuce de Paul White il y a plusieurs années. Bien sûr, vous devez faire attention à ne mettre aucune sorte d'indices de jointure dans le code de production - cela peut échouer si des personnes modifient les objets sous-jacents et que ce type spécifique de jointure n'est plus possible ou plus optimal.

Pour les requêtes plus complexes et lorsque vous passez à SQL Server 2012 ou supérieur, il est possible que l' indicateur de trace 2453 puisse vous aider. Cependant, ce drapeau n'a pas aidé avec cette simple jointure. Et les mêmes clauses de non-responsabilité s'appliqueraient - c'est juste une chose alternative que vous ne devriez généralement pas faire sans une tonne de documentation et des procédures de test de régression rigoureuses en place.

De plus, le Service Pack 1 est hors service depuis longtemps, vous devriez utiliser le Service Pack 3 + MS15-058 .


3

Les variables de table et les tables temporaires sont gérées différemment de plusieurs façons. Il y a une excellente réponse ici avec beaucoup de détails sur où ils sont différents.

Plus précisément dans votre cas, je suppose que le fait que les tables temporaires puissent générer des statistiques supplémentaires et des plans parallèles tandis que les variables de table ont des statistiques plus limitées (pas de statistiques au niveau des colonnes) et aucun plan parallèle n'est votre coupable.

Il est préférable de vider la variable de table dans une table temporaire pour la durée de la procédure stockée.

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.