Por que o ingresso não funciona com sys.query_store_plan?

10

A seguir, é apresentada uma simplificação de um problema de desempenho encontrado no Query Store:

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;

A plan_idcoluna está documentada como sendo a chave primária de sys.query_store_plan, mas o plano de execução não usa a eliminação de junção conforme o esperado:

  1. Nenhum atributo está sendo projetado na DMV.
  2. A chave primária do DMV plan_idnão pode duplicar linhas da tabela temporária
  3. A LEFT JOINé usado, portanto, nenhuma linha de Tpode ser eliminada.

Plano de execução

plano gráfico

Por que isso e o que pode ser feito para obter a eliminação da junção aqui?

Paul White 9
fonte

Respostas:

16

A documentação é um pouco enganadora. A DMV é uma visão não materializada e não possui uma chave primária como tal. As definições subjacentes são um pouco complexas, mas uma definição simplificada de sys.query_store_plané:

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;

Além disso, sys.plan_persist_plan_mergedtambém é uma visualização, embora seja necessário conectar-se através da Conexão de Administrador Dedicado para ver sua definição. Mais uma vez, 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;

Os índices em sys.plan_persist_plan são:

╔════════════════════════╦════════════════════════ ══════════════╦═════════════╗
║ index_name ║ index_description ║ index_keys ║
╠════════════════════════╬════════════════════════ ══════════════╬═════════════╣
║ plan_persist_plan_cidx ║ clusterizado, exclusivo localizado em PRIMARY ║ plan_id ║
║ plan_persist_plan_idx1 ║ não clusterizado localizado no PRIMARY ║ query_id (-) ║
╚════════════════════════╩════════════════════════ ══════════════╩═════════════╝

Então, plan_idé limitado a ser único emsys.plan_persist_plan .

Agora, sys.plan_persist_plan_in_memory é uma função de streaming com valor de tabela, apresentando uma visão tabular dos dados mantidos apenas nas estruturas de memória interna. Como tal, não possui restrições exclusivas.

No fundo, a consulta que está sendo executada é, portanto, 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 não produz eliminação de junção:

insira a descrição da imagem aqui

Chegando ao cerne da questão, o problema é a 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 a junção esquerda pode resultar na @t2duplicação de linhas porque @t3não possui nenhuma restrição de exclusividade plan_id. Portanto, a associação não pode ser eliminada:

insira a descrição da imagem aqui

Para contornar isso, podemos dizer explicitamente ao otimizador que não exigimos nenhum 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;

A junção externa para @t3agora pode ser eliminada:

insira a descrição da imagem aqui

Aplicando isso à 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;

Da mesma forma, poderíamos adicionar em GROUP BY T.plan_idvez de DISTINCT. De qualquer forma, o otimizador agora pode raciocinar corretamente sobre o plan_idatributo até as visualizações aninhadas e eliminar as junções externas conforme desejado:

insira a descrição da imagem aqui

Observe que tornar plan_idexclusivo na tabela temporária não seria suficiente para obter a eliminação da junção, pois não impediria resultados incorretos. Devemos rejeitar explicitamente os plan_idvalores duplicados do resultado final para permitir que o otimizador trabalhe sua mágica aqui.

Paul White 9
fonte