Mise en file d'attente à balayage constant

14

J'ai une table avec quelques dizaines de lignes. La configuration simplifiée suit

CREATE TABLE #data ([Id] int, [Status] int);

INSERT INTO #data
VALUES (100, 1), (101, 2), (102, 3), (103, 2);

Et j'ai une requête qui joint cette table à un ensemble de lignes construites de valeur de table (faites de variables et de constantes), comme

DECLARE @id1 int = 101, @id2 int = 105;

SELECT
    COALESCE(p.[Code], 'X') AS [Code],
    COALESCE(d.[Status], 0) AS [Status]
FROM (VALUES
        (@id1, 'A'),
        (@id2, 'B')
    ) p([Id], [Code])
    FULL JOIN #data d ON d.[Id] = p.[Id];

Le plan d'exécution de la requête montre que la décision de l'optimiseur est d'utiliser la FULL LOOP JOINstratégie, ce qui semble approprié, car les deux entrées ont très peu de lignes. Une chose que j'ai remarquée (et je ne suis pas d'accord), cependant, c'est que les lignes TVC sont mises en file d'attente (voir la zone du plan d'exécution dans l'encadré rouge).

Mise en file d'attente à balayage constant

Pourquoi l'optimiseur introduit le spool ici, quelle est la raison de le faire? Il n'y a rien de complexe au-delà de la bobine. On dirait que ce n'est pas nécessaire. Comment s'en débarrasser dans ce cas, quelles sont les voies possibles?


Le plan ci-dessus a été obtenu le

Microsoft SQL Server 2014 (SP2-CU11) (KB4077063) - 12.0.5579.0 (X64)

i-one
la source
Suggestion connexe sur feedback.azure.com
i-one

Réponses:

19

Pourquoi l'optimiseur introduit le spool ici, quelle est la raison de le faire? Il n'y a rien de complexe au-delà de la bobine.

La chose au-delà du spool n'est pas une simple référence de table, qui pourrait simplement être dupliquée lorsque l' alternative gauche join / anti semi join est générée.

Cela peut ressembler un peu à une table (Constant Scan) mais pour l'optimiseur * c'est une UNION ALLdes lignes séparées dans la VALUESclause.

La complexité supplémentaire est suffisante pour que l'optimiseur choisisse de spouler et de relire les lignes source, et de ne pas remplacer le spouleur par un simple "get table" plus tard. Par exemple, la transformation initiale de la jointure complète ressemble à ceci:

plan précoce

Remarquez les bobines supplémentaires introduites par la transformation générale. Les bobines situées au-dessus d'une table simple sont nettoyées ultérieurement par la règle SpoolGetToGet.

Si l'optimiseur avait une SpoolConstGetToConstGetrègle correspondante , cela pourrait fonctionner comme vous le souhaitez, en principe.

Comment s'en débarrasser dans ce cas, quelles sont les voies possibles?

Utilisez une vraie table (temporaire ou variable), ou écrivez manuellement la transformation à partir de la jointure complète, par exemple:

WITH 
    p([Id], [Code]) AS
    (
        SELECT @id1, 'A'
        UNION ALL
        SELECT @id2, 'B'
    ),
    FullJoin AS
    (
        SELECT
            p.Code,
            d.[Status]
        FROM p
        LEFT JOIN #data d 
            ON d.[Id] = p.[Id]
        UNION ALL
        SELECT
            NULL,
            D.[Status]
        FROM #data AS D
        WHERE NOT EXISTS
        (
            SELECT *
            FROM p
            WHERE p.Id = D.Id
        )
    )
SELECT
    COALESCE(FullJoin.Code, 'X') AS Code,
    COALESCE(FullJoin.Status, 0) AS [Status]
FROM FullJoin;

Planifier une réécriture manuelle:

Plan de réécriture manuelle

Cela a un coût estimé à 0,0067201 unités, contre 0,0203412 unités pour l'original.


* Il peut être observé comme un LogOp_UnionAlldans l' arbre converti (TF 8605). Dans l' arborescence d'entrée (TF 8606), il s'agit d'un LogOp_ConstTableGet. L' arbre converti montre l'arborescence des éléments d'expression de l'optimiseur après l'analyse, la normalisation, l'algèbre, la liaison et certains autres travaux préparatoires. L' arbre d'entrée montre les éléments après la conversion en forme normale de négation (conversion NNF), l'effondrement des constantes d'exécution et quelques autres bits et bobs. La conversion NNF inclut la logique pour réduire les unions logiques et les récupérations de table communes, entre autres.

Paul White 9
la source
3

La bobine de table crée simplement une table à partir des deux ensembles de tuples présents dans la VALUESclause.

Vous pouvez éliminer le spool en insérant d'abord ces valeurs dans une table temporaire, comme ceci:

DROP TABLE IF EXISTS #data;
CREATE TABLE #data ([Id] int, [Status] int);

INSERT INTO #data
VALUES (100, 1), (101, 2), (102, 3), (103, 2);

DROP TABLE IF EXISTS #p;
CREATE TABLE #p
(
    Id int NOT NULL
    , Code char(1) NOT NULL
);

DECLARE @id1 int = 101, @id2 int = 105;

INSERT INTO #p (Id, Code)
VALUES
        (@id1, 'A'),
        (@id2, 'B');


SELECT
    COALESCE(p.[Code], 'X') AS [Code],
    COALESCE(d.[Status], 0) AS [Status]
FROM #p p
    FULL JOIN #data d ON d.[Id] = p.[Id];

En regardant le plan d'exécution de votre requête, nous voyons que la liste de sortie contient deux colonnes qui utilisent le Unionpréfixe; c'est un indice que la bobine crée une table à partir d'une source syndiquée:

entrez la description de l'image ici

le FULL OUTER JOIN requiert que SQL Server accède aux valeurs pdeux fois, une fois pour chaque "côté" de la jointure. La création d'un spool permet à la jointure de boucles internes résultante d'accéder aux données spoule.

Fait intéressant, si vous remplacez le FULL OUTER JOINpar un LEFT JOINet un RIGHT JOINet UNIONles résultats ensemble, SQL Server n'utilise pas de spoule.

SELECT
    COALESCE(p.[Code], 'X') AS [Code],
    COALESCE(d.[Status], 0) AS [Status]
FROM (VALUES
        (101, 'A'),
        (105, 'B')
    ) p([Id], [Code])
    LEFT JOIN #data d ON d.[Id] = p.[Id]
UNION
SELECT
    COALESCE(p.[Code], 'X') AS [Code],
    COALESCE(d.[Status], 0) AS [Status]
FROM (VALUES
        (101, 'A'),
        (105, 'B')
    ) p([Id], [Code])
    RIGHT JOIN #data d ON d.[Id] = p.[Id];

entrez la description de l'image ici

Remarque, je ne suggère pas d'utiliser la UNIONrequête ci-dessus; pour de plus grands ensembles d'entrée, il peut ne pas être plus efficace que le simple que FULL OUTER JOINvous avez déjà.

Max Vernon
la source
Dans votre charge de travail réelle, la bobine est-elle vraiment si coûteuse?
Max Vernon