web-dev-qa-db-ja.com

結合除去がsys.query_store_planで機能しないのはなぜですか?

以下は、クエリストアで発生するパフォーマンスの問題の簡略化です。

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 を使用していません:

  1. DMVから投影される属性はありません。
  2. DMV主キーplan_idは一時テーブルの行を複製できません
  3. A LEFT JOINが使用されているため、Tの行は削除できません。

実行計画

plan graphic

これはなぜですか、ここで結合を排除するために何ができますか?

10
Paul White 9

ドキュメントは少し誤解を招くものです。 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_idsys.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;

...これは結合の削除を生成しません:

enter image description here

問題の核心を正しく理解すると、問題は内部クエリです。

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の行が重複する可能性があります。したがって、結合は削除できません。

enter image description here

これを回避するには、重複する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への外部結合を削除できるようになりました。

enter image description here

それを実際のクエリに適用する:

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属性について正しく推論できるようになり、必要に応じて両方の外部結合を削除できます。

enter image description here

一時テーブルでplan_idを一意にすることは、誤った結果を排除しないため、結合の除去を取得するには十分ではないことに注意してください。ここでオプティマイザがその魔法を働かせるには、最終結果から重複するplan_id値を明示的に拒否する必要があります。

16
Paul White 9