Warum funktioniert die Eliminierungsarbeit nicht mit sys.query_store_plan?

10

Das Folgende ist eine Vereinfachung eines Leistungsproblems, das beim Abfragespeicher auftritt:

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;

Die plan_idSpalte wird als Primärschlüssel von dokumentiert sys.query_store_plan, aber der Ausführungsplan verwendet nicht wie erwartet die Join-Eliminierung :

  1. Von der DMV werden keine Attribute projiziert.
  2. Der DMV-Primärschlüssel plan_idkann keine Zeilen aus der temporären Tabelle duplizieren
  3. A LEFT JOINwird verwendet, sodass keine Zeilen von entfernt werden Tkönnen.

Ausführungsplan

Plan Grafik

Warum ist das so und was kann getan werden, um hier eine Join-Eliminierung zu erreichen?

Paul White 9
quelle

Antworten:

16

Die Dokumentation ist etwas irreführend. Die DMV ist eine nicht materialisierte Ansicht und hat keinen Primärschlüssel als solchen. Die zugrunde liegenden Definitionen sind etwas komplex, aber eine vereinfachte Definition von sys.query_store_planist:

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;

Weiter sys.plan_persist_plan_mergedist auch eine Ansicht, obwohl man eine Verbindung über die dedizierte Administratorverbindung herstellen muss, um ihre Definition zu sehen. Wieder vereinfacht:

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;

Die Indizes sys.plan_persist_plansind:

╔════════════════════════╦════════════════════════ ══════════════╦═════════════╗
║ Indexname ║ Indexbeschreibung ║ Indexschlüssel ║
╠════════════════════════╬════════════════════════ ══════════════╬═════════════╣
║ plan_persist_plan_cidx ║ gruppiert, eindeutig auf PRIMARY ║ plan_id ║
║ plan_persist_plan_idx1 ║ nicht gruppiert auf PRIMARY ║ query_id (-) ║
╚════════════════════════╩════════════════════════ ══════════════╩═════════════╝

Ist plan_idalso gezwungen, einzigartig zu sein sys.plan_persist_plan.

Jetzt sys.plan_persist_plan_in_memoryhandelt es sich um eine Streaming-Tabellenfunktion, die eine tabellarische Ansicht von Daten darstellt, die nur in internen Speicherstrukturen gespeichert sind. Als solches hat es keine eindeutigen Einschränkungen.

Im Kern entspricht die ausgeführte Abfrage daher:

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;

... was keine Join-Eliminierung bewirkt:

Geben Sie hier die Bildbeschreibung ein

Um zum Kern des Problems zu gelangen, ist das Problem die innere Frage:

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;

... eindeutig kann die linke Verknüpfung dazu führen, dass Zeilen @t2dupliziert werden, da @t3keine Eindeutigkeitsbeschränkung besteht plan_id. Daher kann der Join nicht entfernt werden:

Geben Sie hier die Bildbeschreibung ein

Um dies zu umgehen, können wir dem Optimierer ausdrücklich mitteilen, dass wir keine doppelten plan_idWerte benötigen :

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;

Die äußere Verbindung zu @t3kann jetzt entfernt werden:

Geben Sie hier die Bildbeschreibung ein

Anwenden auf die eigentliche Abfrage:

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

Ebenso könnten wir GROUP BY T.plan_idanstelle der hinzufügen DISTINCT. Auf plan_idjeden Fall kann das Optimierungsprogramm das Attribut jetzt korrekt durch die verschachtelten Ansichten führen und beide äußeren Verknüpfungen wie gewünscht entfernen:

Geben Sie hier die Bildbeschreibung ein

Beachten Sie, dass eine plan_ideindeutige Angabe in der temporären Tabelle nicht ausreicht, um eine Join-Eliminierung zu erzielen, da dies keine falschen Ergebnisse ausschließt. Wir müssen doppelte plan_idWerte aus dem Endergebnis explizit ablehnen , damit der Optimierer hier seine Magie entfalten kann.

Paul White 9
quelle