Pour une requête moyennement complexe que j'essaie d'optimiser, j'ai remarqué que la suppression de la clause TOP n
Modifie le plan d'exécution. J'aurais deviné que lorsqu'une requête inclut TOP n
Le moteur de base de données exécuterait la requête en ignorant la clause TOP
, puis à la fin, rétrécirait simplement ce résultat dans le n nombre de lignes demandé. Le plan d'exécution graphique semble indiquer que c'est le cas - TOP
est la "dernière" étape. Mais il semble qu'il se passe encore plus de choses.
Ma question est, comment (et pourquoi) une clause TOP n affecte-t-elle le plan d'exécution d'une requête?
Voici une version simplifiée de ce qui se passe dans mon cas:
La requête fait correspondre les lignes de deux tables, A et B.
Sans la clause TOP
, l'optimiseur estime qu'il y aura 19k lignes du tableau A et 46k lignes du tableau B. Le nombre réel de lignes retournées est de 16k pour A et de 13k pour B. Une correspondance de hachage est utilisée pour se joindre ces deux ensembles de résultats pour un total de 69 lignes (puis un tri est appliqué). Cette requête se produit très rapidement.
Lorsque j'ajoute TOP 1001
, L'optimiseur n'utilise pas de correspondance de hachage; au lieu de cela, il trie d'abord les résultats du tableau A (même estimation/réel de 19k/16k) et effectue une boucle imbriquée avec le tableau B. Le nombre estimé de lignes pour le tableau B est maintenant de 1, et l'étrange est que le TOP n
Affecte directement le nombre estimé d'exécutions (recherche d'index) contre B - il semble toujours être 2n + 1 , ou dans mon cas 2003 . Cette estimation change en conséquence si je change TOP n
. Bien sûr, comme il s'agit d'une jointure imbriquée, le nombre réel d'exécutions est de 16k (le nombre de lignes de la table A) et cela ralentit la requête.
Le scénario réel est un peu plus complexe, mais cela capture l'idée/le comportement de base. Les deux tables sont recherchées à l'aide de recherches d'index. Il s'agit de l'édition SQL Server 2008 R2 Enterprise.
J'aurais deviné que lorsqu'une requête inclut TOP n, le moteur de base de données exécuterait la requête en ignorant la clause TOP, puis à la fin, rétrécirait simplement ce résultat fixé au nombre n de lignes cela a été demandé. Le plan d'exécution graphique semble indiquer que c'est le cas - TOP est la "dernière" étape. Mais il semble qu'il se passe plus de choses.
La façon dont ce qui précède est formulé me fait penser que vous pouvez avoir une image mentale incorrecte de la façon dont une requête s'exécute. Un opérateur dans un plan de requête n'est pas une étape (où l'ensemble de résultats complet d'une étape précédente est évalué par le suivant.
SQL Server utilise un modèle d'exécution en pipeline , où chaque opérateur expose des méthodes comme Init () , GetRow () et Close () . Comme le suggère le nom GetRow () , un opérateur produit une ligne à la fois à la demande (comme requis par son opérateur parent). Ceci est documenté dans la documentation en ligne référence des opérateurs logiques et physiques , avec plus de détails dans mon article de blog Pourquoi les plans de requête s'exécutent à l'envers . Ce modèle de ligne à la fois est essentiel pour former une bonne intuition pour l'exécution des requêtes.
Ma question est, comment (et pourquoi) une clause
TOP
n impacte-t-elle le plan d'exécution d'une requête?
Certaines opérations logiques comme TOP
, les semi-jointures et le FAST n
indice de requête affectent la façon dont l'optimiseur de requête coûte les alternatives du plan d'exécution. L'idée de base est qu'une forme de plan possible pourrait renvoyer les premières n lignes plus rapidement qu'un plan différent optimisé pour renvoyer toutes les lignes.
Par exemple, la jointure de boucles imbriquées indexées est souvent le moyen le plus rapide de renvoyer un petit nombre de lignes, bien que la jointure par hachage ou fusion avec des analyses puisse être plus efficace sur des ensembles plus grands. L'optimiseur de requêtes raisonne sur ces choix en définissant un Objectif de ligne à un point particulier de l'arborescence logique des opérations.
Un objectif de ligne modifie la façon dont les alternatives au plan de requête sont évaluées. L'essentiel est que l'optimiseur commence par chiffrer chaque opérateur comme si l'ensemble de résultats complet était requis, définit un objectif de ligne au point approprié, puis redescend dans l'arborescence du plan en estimant le nombre de lignes qu'il s'attend à devoir examiner pour atteindre l'objectif de la ligne.
Par exemple, une TOP(10)
logique définit un objectif de ligne de 10 à un point particulier de l'arborescence de requête logique. Les coûts des opérateurs menant à l'objectif de ligne sont modifiés pour estimer le nombre de lignes qu'ils doivent produire pour atteindre l'objectif de ligne. Ce calcul peut devenir complexe, il est donc plus facile de comprendre tout cela avec un exemple complet et des plans d'exécution annotés. Les objectifs de ligne peuvent affecter plus que le choix du type de jointure ou si les recherches et les recherches sont préférées aux analyses. Plus de détails à ce sujet ici .
Comme toujours, un plan d'exécution sélectionné sur la base d'un objectif de ligne est soumis aux capacités de raisonnement de l'optimiseur et à la qualité des informations qui lui sont fournies. Tous les plans avec un objectif de ligne ne produiront pas le nombre de lignes requis plus rapidement dans la pratique, mais selon le modèle de calcul des coûts, ce sera le cas.
Lorsqu'un plan d'objectif de ligne s'avère ne pas être plus rapide, il existe généralement des moyens de modifier la requête ou de fournir de meilleures informations à l'optimiseur de sorte que le plan naturellement sélectionné soit le meilleur. L'option appropriée dans votre cas dépend bien sûr des détails. La fonctionnalité d'objectif de ligne est généralement très efficace (bien qu'il y ait un bug à surveiller lorsqu'il est utilisé dans des plans d'exécution parallèles).
Votre requête et votre plan particuliers peuvent ne pas être adaptés à une analyse détaillée ici (fournissez certainement un plan d'exécution réel si vous le souhaitez), mais nous espérons que les idées décrites ici vous permettront d'avancer.
Lorsque vous utilisez TOP, l'Optimizer voit une opportunité de faire moins de travail. Si vous demandez 10 lignes, il y a de fortes chances qu'il n'ait pas besoin de consommer l'ensemble entier. L'opérateur TOP peut donc être poussé beaucoup plus loin vers la droite. Il continuera à demander des lignes à l'opérateur suivant (à sa droite), jusqu'à ce qu'il en ait reçu suffisamment.
Vous indiquez que sans TOP, la requête trie les données à la toute fin. Si le moteur pouvait savoir à l'avance combien de lignes allaient être satisfaites par la jointure, il pourrait bien choisir d'utiliser un plan similaire, en positionnant le TOP sur la gauche. Mais avec l'effort de faire un Hash Match étant relativement élevé, et probablement aucune option pour une fusion, l'Optimizer pourrait préférer filtrer le TOP plus à droite.
Lorsque la table B est interrogée, elle récupère une seule ligne à la fois. C'est pourquoi l'estimation est 1. Elle suppose également qu'elle ne trouvera cette ligne que 50% du temps. Il suppose donc qu'il aura besoin de 2n + 1 cherche à le trouver.