以下は、クエリストアで発生するパフォーマンスの問題の簡略化です。
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;
plan_id
列は sys.query_store_plan
ですが、実行プランは join elimination を使用していません:
plan_id
は一時テーブルの行を複製できませんLEFT JOIN
が使用されているため、T
の行は削除できません。これはなぜですか、ここで結合を排除するために何ができますか?
ドキュメントは少し誤解を招くものです。 DMVは非具体化ビューであり、そのような主キーはありません。基礎となる定義は少し複雑ですが、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;
さらに、sys.plan_persist_plan_merged
もビューですが、その定義を確認するには専用管理者接続を介して接続する必要があります。再度、簡略化しました:
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;
sys.plan_persist_plan
のインデックスは次のとおりです。
╔════════════════════════╦═══════════════════ ═══════════════════╦═════════════╗ ║index_name║index_description║index_keys║ ╠════════════════════════╬═══════════════════════ ═══════════════╬═════════════╣ ║plan_persist_plan_cidx║クラスタ化され、PRIMARYに配置された一意の║plan_id║ ║plan_persist_plan_idx1 PR PRIMARYにある非クラスターcluster query_id(-)║ ╚════════════════════════╩════ ══════════════════════════════════╩═════════════╝
したがって、plan_id
はsys.plan_persist_plan
で一意になるように制約されています。
現在、sys.plan_persist_plan_in_memory
はストリーミングテーブル値関数であり、内部メモリ構造でのみ保持されているデータを表形式で表示します。そのため、固有の制約はありません。
したがって、実行されるクエリは、基本的に次のものと同等です。
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;
...これは結合の削除を生成しません:
問題の核心を正しく理解すると、問題は内部クエリです。
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;
...明らかに@t2
は@t3
に一意性制約がないため、左結合はplan_id
の行が重複する可能性があります。したがって、結合は削除できません。
これを回避するには、重複するplan_id
値を必要としないことをオプティマイザに明示的に通知できます。
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;
@t3
への外部結合を削除できるようになりました。
それを実際のクエリに適用する:
SELECT DISTINCT
T.plan_id
FROM #tears AS T
LEFT JOIN sys.query_store_plan AS QSP
ON QSP.plan_id = T.plan_id;
同様に、DISTINCT
の代わりにGROUP BY T.plan_id
を追加することもできます。とにかく、オプティマイザはネストされたビュー全体にわたってplan_id
属性について正しく推論できるようになり、必要に応じて両方の外部結合を削除できます。
一時テーブルでplan_id
を一意にすることは、誤った結果を排除しないため、結合の除去を取得するには十分ではないことに注意してください。ここでオプティマイザがその魔法を働かせるには、最終結果から重複するplan_id
値を明示的に拒否する必要があります。