Pourquoi la modification de l'ordre de la colonne de jointure déclarée introduit-elle un tri?

40

J'ai deux tables avec des colonnes clés identiques, typées et indexées. L'un d'eux a un index cluster unique , l'autre un non-unique .

La configuration du test

Script d'installation, incluant des statistiques réalistes:

DROP TABLE IF EXISTS #left;
DROP TABLE IF EXISTS #right;

CREATE TABLE #left (
    a       char(4) NOT NULL,
    b       char(2) NOT NULL,
    c       varchar(13) NOT NULL,
    d       bit NOT NULL,
    e       char(4) NOT NULL,
    f       char(25) NULL,
    g       char(25) NOT NULL,
    h       char(25) NULL
    --- and a few other columns
);

CREATE UNIQUE CLUSTERED INDEX IX ON #left (a, b, c, d, e, f, g, h)

UPDATE STATISTICS #left WITH ROWCOUNT=63800000, PAGECOUNT=186000;

CREATE TABLE #right (
    a       char(4) NOT NULL,
    b       char(2) NOT NULL,
    c       varchar(13) NOT NULL,
    d       bit NOT NULL,
    e       char(4) NOT NULL,
    f       char(25) NULL,
    g       char(25) NOT NULL,
    h       char(25) NULL
    --- and a few other columns
);

CREATE CLUSTERED INDEX IX ON #right (a, b, c, d, e, f, g, h)

UPDATE STATISTICS #right WITH ROWCOUNT=55700000, PAGECOUNT=128000;

La repro

Lorsque je joins ces deux tables sur leurs clés de cluster, j'attends une fusion MERGE un à plusieurs, comme ceci:

SELECT *
FROM #left AS l
LEFT JOIN #right AS r ON
    l.a=r.a AND
    l.b=r.b AND
    l.c=r.c AND
    l.d=r.d AND
    l.e=r.e AND
    l.f=r.f AND
    l.g=r.g AND
    l.h=r.h
WHERE l.a='2018';

C'est le plan de requête que je veux:

C'est ce que je veux.

(Peu importe les avertissements, ils ont à voir avec les fausses statistiques.)

Cependant, si je change l'ordre des colonnes autour de la jointure, comme ceci:

SELECT *
FROM #left AS l
LEFT JOIN #right AS r ON
    l.c=r.c AND     -- used to be third
    l.a=r.a AND     -- used to be first
    l.b=r.b AND     -- used to be second
    l.d=r.d AND
    l.e=r.e AND
    l.f=r.f AND
    l.g=r.g AND
    l.h=r.h
WHERE l.a='2018';

... ça arrive:

Plan de requête après modification de l'ordre des colonnes déclaré dans la jointure.

L'opérateur de tri semble ordonner les flux en fonction de l'ordre déclaré de la jointure, c'est c, a, b, d, e, f, g, h-à- dire , ce qui ajoute une opération de blocage à mon plan de requête.

Choses que j'ai regardées

  • J'ai essayé de changer les colonnes pour obtenir les NOT NULLmêmes résultats.
  • La table d'origine a été créée avec ANSI_PADDING OFF, mais sa création ANSI_PADDING ONn'affecte pas ce plan.
  • J'ai essayé un INNER JOINau lieu de LEFT JOIN, pas de changement.
  • Je l'ai découvert sur un SP2 Enterprise 2014, j'ai créé une repro sur un développeur 2017 (CU actuelle).
  • La suppression de la clause WHERE sur la colonne d’indexation principale génère le bon plan, mais cela affecte en quelque sorte les résultats .. :)

Enfin, nous arrivons à la question

  • Est-ce intentionnel?
  • Puis-je éliminer le tri sans changer la requête (qui est le code du fournisseur, donc je préfère ne pas le faire ...). Je peux changer la table et les index.
Daniel Hutmacher
la source

Réponses:

28

Est-ce intentionnel?

C'est par conception, oui. La meilleure source publique pour cette assertion a malheureusement été perdue lorsque Microsoft a retiré le site de commentaires de Connect, effaçant de nombreux commentaires utiles des développeurs de l'équipe SQL Server.

Quoi qu'il en soit, la conception actuelle de l'optimiseur ne cherche pas activement à éviter les tris inutiles en soi . C'est le cas le plus souvent rencontré avec les fonctions de fenêtrage, etc., mais également chez d'autres opérateurs sensibles aux commandes, et en particulier aux commandes conservées entre opérateurs.

Néanmoins, l'optimiseur est assez efficace (dans de nombreux cas) pour éviter un tri inutile, mais ce résultat se produit normalement pour des raisons autres que d'essayer de manière agressive différentes combinaisons d'ordonnancement. En ce sens, il ne s'agit pas tant de "l'espace de recherche" que des interactions complexes entre les fonctions de l'optimiseur orthogonal, dont il a été démontré qu'elles amélioraient la qualité générale du plan à un coût acceptable.

Par exemple, le tri peut souvent être évité simplement en faisant correspondre une exigence de classement (par exemple, niveau supérieur ORDER BY) à un index existant. Trivialement dans votre cas, cela pourrait signifier l’ajout, ORDER BY l.a, l.b, l.c, l.d, l.e, l.f, l.g, l.h;mais c’est une simplification excessive (et inacceptable car vous ne souhaitez pas modifier la requête).

Plus généralement, chaque groupe de mémos peut être associé à des propriétés requises ou souhaitées, qui peuvent inclure un ordre d'entrée. Lorsqu'il n'existe aucune raison évidente d' appliquer un ordre particulier (par exemple, pour satisfaire un ORDER BYopérateur physique sensible à l'ordre ou pour obtenir des résultats corrects), un élément de «chance» est impliqué. J'ai écrit davantage sur les spécificités de cette opération en ce qui concerne la fusion de jointures (en mode union ou jointure) pour éviter le tri avec la fusion de jointures de concaténation . Une grande partie de ces éléments dépasse la surface prise en charge du produit. Traitez-le donc comme étant informatif et susceptible de changer.

Dans votre cas particulier, oui, vous pouvez ajuster l'indexation comme le suggère jadarnel27 pour éviter les tris; bien qu'il y ait peu de raisons de préférer une fusion, rejoignez-la ici. Vous pouvez également suggérer un choix entre une jointure physique hachée ou en boucle avec l' OPTION(HASH JOIN, LOOP JOIN)utilisation d'un guide de planification sans modifier la requête, selon votre connaissance des données et le compromis entre performances optimales, optimales et moyennes.

Enfin, comme curiosité, notez que les tris peuvent être évités avec une simple ORDER BY l.b, au prix d’une fusion potentiellement moins efficace de la fusion plusieurs à plusieurs b, avec un résidu complexe. Je le mentionne surtout pour illustrer l’interaction entre les fonctions de l’optimiseur que j’ai mentionnées précédemment et la façon dont les exigences de haut niveau peuvent se propager.

Paul White dit GoFundMonica
la source
19

Puis-je éliminer le tri sans changer la requête (qui est le code du fournisseur, donc je préfère ne pas le faire ...). Je peux changer la table et les index.

Si vous pouvez modifier les index, modifier l'ordre de l'index #rightafin qu'il corresponde à celui des filtres de la jointure supprime le tri (pour moi):

CREATE CLUSTERED INDEX IX ON #right (c, a, b, d, e, f, g, h)

De manière surprenante (du moins pour moi), il en résulte qu'aucune requête ne se retrouve avec une sorte.

Est-ce intentionnel?

En regardant la sortie de certains indicateurs de trace étranges , il y a une différence intéressante dans la structure finale de Memo:

capture d'écran de la structure finale du mémo pour chaque requête

Comme vous pouvez le voir dans le "Groupe racine" en haut, les deux requêtes ont la possibilité d'utiliser une jointure de fusion en tant qu'opération physique principale pour exécuter cette requête.

Bonne requête

La jointure sans le tri est gérée par le groupe 29 option 1 et le groupe 31 option 1 (chacun d'entre eux étant des balayages de plage sur les index impliqués). Il est filtré par le groupe 27 (non représenté), qui est la série d'opérations de comparaison logique qui filtrent la jointure.

Mauvaise requête

Celui avec le tri est déterminé par les (nouvelles) options 3 de chacun de ces deux groupes (29 et 31). L'option 3 effectue un tri physique sur les résultats des analyses de plage mentionnées précédemment (option 1 de chacun de ces groupes).

Pourquoi?

Pour une raison quelconque, l'option d'utiliser directement 29.1 et 31.1 en tant que sources pour la jointure de fusion n'est même pas disponible pour l'optimiseur dans la seconde requête. Sinon, je pense qu'il serait répertorié sous le groupe racine parmi les autres options. S'il était disponible, il choisirait certainement ceux qui opèrent au-delà des opérations de tri massivement plus coûteuses.

Je ne peux que conclure que soit:

  • c'est un bug (ou plus probablement une limitation) dans l'algorithme de recherche de l'optimiseur
    • changer les index et les jointures pour n'avoir que 5 clés supprime le tri pour la seconde requête (6, 7 et 8 clés ont le tri).
    • Cela implique que l'espace de recherche avec 8 clés est si grand que l'optimiseur n'a tout simplement pas le temps d'identifier la solution sans tri comme une option viable avant de se terminer plus tôt avec la raison "plan suffisamment bon trouvé"
    • il me semble un peu bizarre que l'ordre des conditions de jointure influence autant le processus de recherche de l'optimiseur, mais c'est vraiment un peu au-dessus de ma tête
  • le tri est nécessaire pour assurer l'exactitude des résultats
    • celle-ci semble improbable, car la requête peut s'exécuter sans le tri lorsqu'il y a moins de clés ou si les clés sont spécifiées dans un ordre différent

J'espère que quelqu'un pourra venir et expliquer pourquoi le tri est nécessaire, mais je pensais que la différence dans le bâtiment Memo était suffisamment intéressante pour que l'on puisse y répondre.

Josh Darnell
la source
1
Je pense que votre commentaire sur l'espace de recherche est en fait le cas ici. Afin de n'utiliser que les index, l'optimiseur doit vérifier qu'elles sont suffisantes pour les conditions. Après 5 clés, il y a trop de possibilités à vérifier avant de devoir revenir en arrière. Je serais curieux de savoir si toutes les combinaisons d'ordre de la requête étaient énumérées, combien l'optimiseur réussirait par rapport au repli
Mr.Mindor
Et oui, l’incohérence semble un peu déréglée, mais elle dépend probablement entièrement de l’algorithme utilisé pour vérifier que les index sont suffisants. Si toutes les combinaisons étaient testées, vous seriez probablement en mesure de voir le modèle dans les résultats et de déterminer quel algorithme est utilisé. Je parierais qu'il est écrit pour fonctionner de manière optimale pour les cas d'utilisation plus typiques. Il peut exister une alternative capable de trouver la solution à 8 clés de manière fiable dans le délai imparti, mais elle est plus lente que la solution actuelle lorsque le nombre de clés est inférieur à 3-4.
Mr.Mindor