web-dev-qa-db-fra.com

Mise en file d'attente à balayage constant

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 par 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 FULL LOOP JOIN stratégie, qui semble appropriée, 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).

Constant Scan spooling

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)

14
i-one

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 la jointure gauche/anti semi-jointure alternative est générée.

Il peut ressembler un peu à une table (Constant Scan) mais pour l'optimiseur * c'est un UNION ALL des lignes séparées de la clause VALUES.

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:

early plan

Remarquez les bobines supplémentaires introduites par la transformation générale. Les spools au-dessus d'une simple table get sont nettoyés plus tard par la règle SpoolGetToGet.

Si l'optimiseur avait une règle SpoolConstGetToConstGet 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:

Manual rewrite plan

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_UnionAll dans l'arborescence convertie (TF 8605). Dans l'arborescence d'entrée (TF 8606), il s'agit d'un LogOp_ConstTableGet. L'arbre converti montre l'arbre 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 affiche 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.

19
Paul White 9

Le spouleur de table crée simplement une table à partir des deux ensembles de tuples présents dans la clause VALUES.

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 préfixe Union; c'est un indice que la bobine crée une table à partir d'une source syndiquée:

enter image description here

Le FULL OUTER JOIN nécessite que SQL Server accède aux valeurs dans p deux 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 JOIN avec un LEFT JOIN et un RIGHT JOIN et UNION les 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];

enter image description here

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

3
Max Vernon