Perché il join non funziona con sys.query_store_plan?


10

Di seguito è una semplificazione di un problema di prestazioni riscontrato con il 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;

La plan_idcolonna è documentata come chiave primaria di sys.query_store_plan, ma il piano di esecuzione non utilizza l' eliminazione del join come ci si aspetterebbe:

  1. Nessun attributo viene proiettato dal DMV.
  2. La chiave primaria DMV plan_idnon può duplicare le righe dalla tabella temporanea
  3. Viene LEFT JOINutilizzato A, quindi non è Tpossibile eliminare righe da .

Progetto esecutivo

piano grafico

Perché è questo e cosa si può fare per ottenere l'eliminazione dei join qui?

Risposte:


16

La documentazione è un po 'fuorviante. Il DMV è una vista non materializzata e non ha una chiave primaria in quanto tale. Le definizioni sottostanti sono un po 'complesse ma una definizione semplificata di 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;

Inoltre, sys.plan_persist_plan_mergedè anche una vista, anche se è necessario connettersi tramite la Connessione amministratore dedicata per vedere la sua definizione. Ancora una volta, semplificato:

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;

Gli indici su sys.plan_persist_plansono:

╔════════════════════════╦════════════════════════ ══════════════╦═════════════╗
║ nome_indice ║ descrizione_indice ║ tasti_indice ║
╠════════════════════════╬════════════════════════ ══════════════╬═════════════╣
║ plan_persist_plan_cidx ║ cluster, unico situato su PRIMARY ║ plan_id ║
║ plan_persist_plan_idx1 ║ non cluster situato su PRIMARY ║ query_id (-) ║
╚════════════════════════╩════════════════════════ ══════════════╩═════════════╝

Quindi plan_idè costretto a essere unico sys.plan_persist_plan.

Ora, sys.plan_persist_plan_in_memoryè una funzione con valori di tabella di streaming, che presenta una vista tabellare dei dati contenuti solo nelle strutture di memoria interna. Come tale, non ha vincoli univoci.

In sostanza, la query in esecuzione è quindi 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;

... che non produce eliminazione dei join:

inserisci qui la descrizione dell'immagine

Giungendo al nocciolo del problema, il problema è la query 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;

... chiaramente il join sinistro potrebbe comportare la @t2duplicazione delle righe perché @t3non ha vincoli di unicità plan_id. Pertanto, il join non può essere eliminato:

inserisci qui la descrizione dell'immagine

Per ovviare a questo, possiamo dire esplicitamente all'ottimizzatore che non abbiamo bisogno di plan_idvalori duplicati :

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;

Il join esterno su @t3può ora essere eliminato:

inserisci qui la descrizione dell'immagine

Applicandolo alla query reale:

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

Allo stesso modo, potremmo aggiungere GROUP BY T.plan_idinvece di DISTINCT. Ad ogni modo, l'ottimizzatore ora può ragionare correttamente sull'attributo plan_idfino in fondo attraverso le viste nidificate ed eliminare entrambi i join esterni come desiderato:

inserisci qui la descrizione dell'immagine

Notare che rendere plan_idunivoco nella tabella temporanea non sarebbe sufficiente per ottenere l'eliminazione del join, poiché non precluderebbe risultati errati. Dobbiamo rifiutare esplicitamente i plan_idvalori duplicati dal risultato finale per consentire all'ottimizzatore di fare la sua magia qui.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.