Spooling de escaneo constante

14

Tengo una mesa con unas pocas docenas de filas. La configuración simplificada está siguiendo

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

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

Y tengo una consulta que une esta tabla a un conjunto de filas construidas de valores de tabla (hechas de variables y constantes), como

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];

El plan de ejecución de consultas muestra que la decisión del optimizador es usar la FULL LOOP JOINestrategia, lo que parece apropiado, ya que ambas entradas tienen muy pocas filas. Sin embargo, una cosa que noté (y no puedo estar de acuerdo) es que las filas de TVC se están poniendo en cola (vea el área del plan de ejecución en el cuadro rojo).

Spooling de escaneo constante

¿Por qué el optimizador introduce el carrete aquí, cuál es la razón para hacerlo? No hay nada complejo más allá del carrete. Parece que no es necesario. ¿Cómo deshacerse de él en este caso, cuáles son las formas posibles?


El plan anterior se obtuvo el

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

i-one
fuente
Sugerencia relacionada en feedback.azure.com
i-one

Respuestas:

19

¿Por qué el optimizador introduce el carrete aquí, cuál es la razón para hacerlo? No hay nada complejo más allá del carrete.

Lo que está más allá del spool no es una simple referencia de tabla, que simplemente podría duplicarse cuando se genera la alternativa de unión izquierda / anti semi unión .

Puede parecerse un poco a una tabla (Constant Scan) pero para el optimizador * es una UNION ALLde las filas separadas en la VALUEScláusula.

La complejidad adicional es suficiente para que el optimizador elija poner en cola y reproducir las filas de origen, y no reemplazar la cola con un simple "table get" más adelante. Por ejemplo, la transformación inicial de la unión completa se ve así:

plan temprano

Observe los carretes adicionales introducidos por la transformación general. Los carretes encima de una tabla simple se limpian más tarde por la regla SpoolGetToGet.

Si el optimizador tuviera una SpoolConstGetToConstGetregla correspondiente , en principio podría funcionar como lo desee.

¿Cómo deshacerse de él en este caso, cuáles son las formas posibles?

Use una tabla real (temporal o variable), o escriba la transformación desde la unión completa manualmente, por ejemplo:

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;

Plan para la reescritura manual:

Plan de reescritura manual

Esto tiene un costo estimado de 0.0067201 unidades, en comparación con 0.0203412 unidades para el original.


* Se puede observar como a LogOp_UnionAllen el Árbol convertido (TF 8605). En el árbol de entrada (TF 8606) es a LogOp_ConstTableGet. El árbol convertido muestra el árbol de elementos de expresión del optimizador después del análisis, la normalización, la algebrización, el enlace y algunos otros trabajos preparatorios. El árbol de entrada muestra los elementos después de la conversión a la forma normal de negación (conversión NNF), el colapso constante del tiempo de ejecución y algunos otros bits y oscilaciones. La conversión NNF incluye lógica para colapsar uniones lógicas y obtener tablas comunes, entre otras cosas.

Paul White 9
fuente
3

El carrete de tabla simplemente está creando una tabla a partir de los dos conjuntos de tuplas presentes en la VALUEScláusula.

Puede eliminar el carrete insertando esos valores en una tabla temporal primero, de esta manera:

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];

Al observar el plan de ejecución de su consulta, vemos que la lista de salida contiene dos columnas que usan el Unionprefijo; Esta es una pista de que el spool está creando una tabla desde una fuente sindicalizada:

ingrese la descripción de la imagen aquí

los FULL OUTER JOIN requiere SQL Server para acceder a los valores en pdos veces, una para cada "lado" de la combinación. La creación de un spool permite que los bucles internos resultantes se unan para acceder a los datos en spool.

Curiosamente, si reemplaza el FULL OUTER JOINcon ay LEFT JOINa RIGHT JOIN, y UNIONlos resultados juntos, SQL Server no usa un spool.

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];

ingrese la descripción de la imagen aquí

Tenga en cuenta que no sugiero usar la UNIONconsulta anterior; para conjuntos de entrada más grandes, puede que no sea más eficiente que el simple FULL OUTER JOINque ya tiene.

Max Vernon
fuente
En su carga de trabajo real, ¿el carrete es realmente tan costoso?
Max Vernon el