web-dev-qa-db-fra.com

Pourquoi cette requête n'utilise-t-elle pas une bobine d'index?

Je pose cette question afin de mieux comprendre le comportement de l'optimiseur et de comprendre les limites autour des spools d'index. Supposons que je mette des entiers de 1 à 10000 dans un tas:

CREATE TABLE X_10000 (ID INT NOT NULL);
truncate table X_10000;

INSERT INTO X_10000 WITH (TABLOCK)
SELECT TOP 10000 ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
FROM master..spt_values t1
CROSS JOIN master..spt_values t2;

Et forcer une jointure en boucle imbriquée avec MAXDOP 1:

SELECT *
FROM X_10000 a
INNER JOIN X_10000 b ON a.ID = b.ID
OPTION (LOOP JOIN, MAXDOP 1);

Il s'agit d'une action plutôt hostile à entreprendre envers SQL Server. Les jointures de boucles imbriquées ne sont souvent pas un bon choix lorsque les deux tables n'ont pas d'index pertinents. Voici le plan:

bad query

La requête prend 13 secondes sur ma machine avec 100000000 lignes extraites de la bobine de table. Cependant, je ne vois pas pourquoi la requête doit être lente. L'optimiseur de requêtes a la possibilité de créer des index à la volée spools d'index . Cette requête semble être un candidat parfait pour une bobine d'index.

La requête suivante renvoie les mêmes résultats que la première, possède une bobine d'index et se termine en moins d'une seconde:

SELECT *
FROM X_10000 a
CROSS APPLY (SELECT TOP (9223372036854775807) b.ID FROM X_10000 b WHERE a.ID = b.ID) ca
OPTION (LOOP JOIN, MAXDOP 1);

workaround 1

Cette requête a également une bobine d'indexation et se termine en moins d'une seconde:

SELECT *
FROM X_10000 a
INNER JOIN X_10000 b ON a.ID >= b.ID AND a.ID <= b.ID
OPTION (LOOP JOIN, MAXDOP 1);

workaround 2

Pourquoi la requête d'origine n'a-t-elle pas de bobine d'index? Existe-t-il un ensemble d'indications ou d'indicateurs de trace documentés ou non qui lui donneront une bobine d'index? J'ai trouvé cette question connexe , mais cela ne répond pas entièrement à ma question et je ne peux pas faire fonctionner le mystérieux indicateur de trace pour cette requête.

23
Joe Obbish

Comme vous le savez, la recherche de l'optimiseur n'est pas exhaustive. Il essaie des choses qui ont du sens dans le contexte et qui paient souvent des dividendes sur les requêtes réelles. Forcer une jointure en boucle entre deux tables de segments non indexés à une seule colonne n'est pas un tel scénario. Cela dit, voici quelques détails:

SQL Server aime transformer tôt s'applique aux jointures, car il connaît plus d'astuces avec les jointures. Plus tard, il pourra envisager de reconvertir la jointure en demande. Les différence entre les deux étant des paramètres corrélés (références externes). S'applique a un sens quand il y a un index approprié sur le côté intérieur. Votre exemple n'a pas d'index, donc l'optimiseur n'est pas convaincu d'explorer la traduction vers une application.

Une jointure simple (non applicable) a le prédicat de jointure sur l'opérateur de jointure au lieu des références externes. L'optimisation du spool pour une non-application est généralement un spool de table paresseux, car il n'y a pas de prédicat du côté interne, uniquement au niveau de la jointure.

L'optimiseur n'envisage pas de créer un index à la volée pour activer une application; la séquence des événements est plutôt l'inverse: transformez pour appliquer car il existe un bon index.

Vous pouvez parfois encourager une application plutôt qu'une jointure en utilisant la syntaxe APPLY dans votre requête. L'indicateur de trace non documenté 9114 peut aider à cela en dissuadant l'optimiseur de traduire une application logique en une jointure initiale. Par exemple:

SELECT * 
FROM dbo.X_1000 AS a
CROSS APPLY (SELECT * FROM dbo.X_1000 AS b WHERE b.ID = a.ID) AS b
OPTION (QUERYTRACEON 9114);

Spool plan

Une bobine d'index est privilégiée pour appliquer car la référence externe signifie que la sélection est appliquée sur le côté interne de la jointure. Vous le verrez souvent via SelToIndexOnTheFly mais d'autres chemins existent. Voir mon article The Eager Index Spool and The Optimizer .

21
Paul White 9