Pourquoi ces requêtes similaires utilisent-elles différentes phases d'optimisation (traitement des transactions vs plan rapide)?

12

L'exemple de code dans cet élément de connexion

Affiche un bug où

SELECT COUNT(*)
FROM   dbo.my_splitter_1('2') L1
       INNER JOIN dbo.my_splitter_1('') L2
         ON L1.csv_item = L2.csv_item

Renvoie les résultats corrects. Mais ce qui suit renvoie des résultats incorrects (en 2014 en utilisant le nouvel estimateur de cardinalité)

SELECT
    (SELECT COUNT(*)
    FROM dbo.my_splitter_1('2') L1
     INNER JOIN dbo.my_splitter_1('') L2
        ON L1.csv_item = L2.csv_item)

Comme il charge de manière incorrecte les résultats pour L2 dans une bobine de sous-expression commune, puis relit le résultat de celui pour le résultat L1.

J'étais curieux de savoir pourquoi la différence de comportement entre les deux requêtes. L'indicateur de trace 8675 montre que celui qui fonctionne entre search(0) - transaction processinget celui qui échoue entre search(1) - quick plan.

Je suppose donc que la disponibilité de règles de transformation supplémentaires est à l'origine de la différence de comportement (la désactivation de BuildGbApply ou GenGbApplySimple semble le corriger par exemple).

Mais pourquoi les deux plans pour ces requêtes très similaires rencontrent-ils différentes phases d'optimisation? D'après ce que j'ai lu, il search (0)faut au moins trois tableaux et cette condition n'est certainement pas remplie dans le premier exemple.

Martin Smith
la source

Réponses:

7

Chaque étape a des conditions d'entrée. "Avoir au moins trois références de table" est l'une des conditions d'entrée dont nous parlons lorsque nous donnons des exemples simples, mais ce n'est pas la seule.

En règle générale, seules les jointures et les unions de base sont autorisées à entrer dans la recherche 0; les sous-requêtes scalaires, les semi-jointures, etc. empêchent l'entrée de rechercher 0. Cette étape concerne vraiment les formes de requête de type OLTP très courantes. Les règles nécessaires pour explorer les choses les moins courantes ne sont tout simplement pas activées. Votre exemple de requête a une sous-requête scalaire, donc il échoue à la saisie.

Cela dépend également de la façon dont vous comptez les références de table. Je n'ai jamais approfondi cela avec les fonctions, mais il est possible que la logique compte les fonctions de valeur de table ainsi que les variables de table qu'elles produisent. Il pourrait même être en train de compter la référence de la table à l'intérieur de la fonction elle-même - je ne suis pas sûr; même si je sais que les fonctions sont un travail difficile tout au long.

Le bug avec GenGbApplySimpleest moche. Cette forme de plan était toujours une possibilité, mais rejetée pour des raisons de coût jusqu'à ce que le changement à la cardinalité variable de table supposée à 100 lignes soit entré. Il est possible de forcer la forme de plan problématique sur le CE pré-2014 avec un USE PLANindice, par exemple.

Vous avez raison de dire que le nouvel élément Connect est le même problème signalé précédemment .

Pour fournir un exemple, la requête suivante se qualifie pour la recherche 0:

DECLARE @T AS table (c1 integer NULL);

SELECT U.c1, rn = ROW_NUMBER() OVER (ORDER BY U.c1) 
FROM 
(
    SELECT c1 FROM @T AS T
    UNION
    SELECT c1 FROM @T AS T
    UNION
    SELECT c1 FROM @T AS T
) AS U;

Faire un petit changement pour inclure une sous-requête scalaire signifie qu'il va directement à la recherche 1:

DECLARE @T AS table (c1 integer NULL);

SELECT U.c1, rn = ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) -- Changed!
FROM 
(
    SELECT c1 FROM @T AS T
    UNION
    SELECT c1 FROM @T AS T
    UNION
    SELECT c1 FROM @T AS T
) AS U;
Paul White 9
la source