La documentation est un peu trompeuse. Le DMV est une vue non matérialisée et n'a pas de clé primaire en tant que telle. Les définitions sous-jacentes sont un peu complexes mais une définition simplifiée de sys.query_store_plan
est:
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;
En outre, sys.plan_persist_plan_merged
est également une vue, bien qu'il soit nécessaire de se connecter via la connexion administrateur dédiée pour voir sa définition. Encore une fois, simplifié:
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;
Les index sur sys.plan_persist_plan
sont:
╔════════════════════════╦════════════════════════ ══════════════╦═════════════╗
║ nom_index ║ description_index ║ clés_index ║
╠════════════════════════╬════════════════════════ ══════════════╬═════════════╣
║ plan_persist_plan_cidx ║ clusterisé, unique situé sur PRIMARY ║ plan_id ║
║ plan_persist_plan_idx1 ║ non cluster situé sur PRIMARY ║ query_id (-) ║
╚════════════════════════╩════════════════════════ ══════════════╩═════════════╝
Il plan_id
est donc contraint d'être unique sur sys.plan_persist_plan
.
Maintenant, sys.plan_persist_plan_in_memory
est une fonction de table en continu, présentant une vue tabulaire des données uniquement conservées dans les structures de mémoire interne. En tant que tel, il n'a pas de contraintes uniques.
Au fond, la requête en cours d'exécution est donc équivalente à:
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;
... qui ne produit pas d'élimination des jointures:
Pour aller directement au cœur du problème, le problème est la requête interne:
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;
... il est clair que la jointure gauche peut entraîner la @t2
duplication des lignes car elle @t3
n'a pas de contrainte d'unicité plan_id
. Par conséquent, la jointure ne peut pas être éliminée:
Pour contourner ce problème, nous pouvons explicitement dire à l'optimiseur que nous n'avons pas besoin de plan_id
valeurs en double :
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 jointure externe à @t3
peut maintenant être éliminée:
Appliquer cela à la vraie requête:
SELECT DISTINCT
T.plan_id
FROM #tears AS T
LEFT JOIN sys.query_store_plan AS QSP
ON QSP.plan_id = T.plan_id;
De même, nous pourrions ajouter GROUP BY T.plan_id
au lieu de DISTINCT
. Quoi qu'il en soit, l'optimiseur peut désormais raisonner correctement sur l' plan_id
attribut tout au long des vues imbriquées et éliminer les deux jointures externes comme vous le souhaitez:
Notez que rendre plan_id
unique dans la table temporaire ne serait pas suffisant pour obtenir l'élimination des jointures, car cela n'empêcherait pas des résultats incorrects. Nous devons explicitement rejeter les plan_id
valeurs en double du résultat final pour permettre à l'optimiseur de travailler sa magie ici.