¿Por qué no funciona la eliminación de combinación con sys.query_store_plan?

10

La siguiente es una simplificación de un problema de rendimiento encontrado con el almacén de consultas:

CREATE TABLE #tears
(
    plan_id bigint NOT NULL
);

INSERT #tears (plan_id) 
VALUES (1);

SELECT
    T.plan_id
FROM #tears AS T
LEFT JOIN sys.query_store_plan AS QSP
    ON QSP.plan_id = T.plan_id;

La plan_idcolumna está documentada como la clave principal de sys.query_store_plan, pero el plan de ejecución no utiliza la eliminación de combinación como se esperaría:

  1. No se proyectan atributos desde el DMV.
  2. La clave primaria del DMV plan_idno puede duplicar filas de la tabla temporal
  3. Se LEFT JOINutiliza A, por lo que no Tse pueden eliminar filas de .

Plan de ejecución

plan gráfico

¿Por qué es esto y qué se puede hacer para obtener la eliminación de la unión aquí?

Paul White 9
fuente

Respuestas:

16

La documentación es un poco engañosa. El DMV es una vista no materializada y no tiene una clave principal como tal. Las definiciones subyacentes son un poco complejas, pero una definición simplificada de sys.query_store_planes:

CREATE VIEW sys.query_store_plan AS
SELECT
    PPM.plan_id
    -- various other attributes
FROM sys.plan_persist_plan_merged AS PPM
LEFT JOIN sys.syspalvalues AS P
    ON P.class = 'PFT' 
    AND P.[value] = plan_forcing_type;

Además, sys.plan_persist_plan_mergedtambién es una vista, aunque es necesario conectarse a través de la Conexión de administrador dedicada para ver su definición. De nuevo, simplificado:

CREATE VIEW sys.plan_persist_plan_merged AS   
SELECT     
    P.plan_id as plan_id,    
    -- various other attributes
FROM sys.plan_persist_plan P 
    -- NOTE - in order to prevent potential deadlock
    -- between QDS_STATEMENT_STABILITY LOCK and index locks   
    WITH (NOLOCK) 
LEFT JOIN sys.plan_persist_plan_in_memory PM
    ON P.plan_id = PM.plan_id;

Los índices en sys.plan_persist_planson:

╔════════════════════════╦════════════════════════ ══════════════╦═════════════╗
║ index_name ║ index_description ║ index_keys ║
╠════════════════════════╬════════════════════════ ══════════════╬═════════════╣
║ plan_persist_plan_cidx ║ agrupado, ubicado en PRIMARY ║ plan_id ║
║ plan_persist_plan_idx1 ║ no agrupado ubicado en PRIMARY ║ query_id (-) ║
╚════════════════════════╩════════════════════════ ══════════════╩═════════════╝

Por plan_idlo tanto, está obligado a ser único en sys.plan_persist_plan.

Ahora, sys.plan_persist_plan_in_memoryes una función de transmisión de valores de tabla, que presenta una vista tabular de los datos que solo se encuentran en las estructuras de la memoria interna. Como tal, no tiene restricciones únicas.

En el fondo, la consulta que se ejecuta es, por lo tanto, equivalente a:

DECLARE @t1 table (plan_id integer NOT NULL);
DECLARE @t2 table (plan_id integer NOT NULL UNIQUE CLUSTERED);
DECLARE @t3 table (plan_id integer NULL);

SELECT 
    T1.plan_id
FROM @t1 AS T1 
LEFT JOIN
(
    SELECT 
        T2.plan_id
    FROM @t2 AS T2
    LEFT JOIN @t3 AS T3 
        ON T3.plan_id = T2.plan_id
) AS Q1
    ON Q1.plan_id = T1.plan_id;

... que no produce eliminación conjunta:

ingrese la descripción de la imagen aquí

Llegando directamente al núcleo del problema, el problema es la consulta interna:

DECLARE @t2 table (plan_id integer NOT NULL UNIQUE CLUSTERED);
DECLARE @t3 table (plan_id integer NULL);

SELECT 
    T2.plan_id
FROM @t2 AS T2
LEFT JOIN @t3 AS T3 
    ON T3.plan_id = T2.plan_id;

... claramente, la unión izquierda puede hacer que las filas @t2se dupliquen porque @t3no tiene restricción de unicidad plan_id. Por lo tanto, la unión no se puede eliminar:

ingrese la descripción de la imagen aquí

Para solucionar esto, podemos decirle explícitamente al optimizador que no requerimos ningún plan_idvalor duplicado :

DECLARE @t2 table (plan_id integer NOT NULL UNIQUE CLUSTERED);
DECLARE @t3 table (plan_id integer NULL);

SELECT DISTINCT
    T2.plan_id
FROM @t2 AS T2
LEFT JOIN @t3 AS T3 
    ON T3.plan_id = T2.plan_id;

La unión externa a @t3ahora se puede eliminar:

ingrese la descripción de la imagen aquí

Aplicando eso a la consulta real:

SELECT DISTINCT
    T.plan_id
FROM #tears AS T
LEFT JOIN sys.query_store_plan AS QSP
    ON QSP.plan_id = T.plan_id;

Igualmente, podríamos agregar en GROUP BY T.plan_idlugar de DISTINCT. De todos modos, el optimizador ahora puede razonar correctamente sobre el plan_idatributo a través de las vistas anidadas y eliminar ambas uniones externas como desee:

ingrese la descripción de la imagen aquí

Tenga en cuenta que hacer plan_idúnico en la tabla temporal no sería suficiente para obtener la eliminación de la unión, ya que no excluiría resultados incorrectos. Debemos rechazar explícitamente plan_idvalores duplicados del resultado final para permitir que el optimizador haga su magia aquí.

Paul White 9
fuente