web-dev-qa-db-ja.com

常時スキャンスプーリング

数十行のテーブルがあります。簡略化されたセットアップは次のとおりです

CREATE TABLE #data ([Id] int, [Status] int);

INSERT INTO #data
VALUES (100, 1), (101, 2), (102, 3), (103, 2);

そして、私はこのテーブルを一連のテーブル値で構成された行(変数と定数で構成される)に結合するクエリを持っています。

DECLARE @id1 int = 101, @id2 int = 105;

SELECT
    COALESCE(p.[Code], 'X') AS [Code],
    COALESCE(d.[Status], 0) AS [Status]
FROM (VALUES
        (@id1, 'A'),
        (@id2, 'B')
    ) p([Id], [Code])
    FULL JOIN #data d ON d.[Id] = p.[Id];

クエリ実行プランは、オプティマイザの決定がFULL LOOP JOIN戦略を使用することであることを示しています。これは、両方の入力に行がほとんどないため、適切と思われます。しかし、私が気付いた(同意できない)ことの1つは、TVC行がスプールされていることです(赤いボックスの実行プランの領域を参照)。

Constant Scan spooling

オプティマイザがここにスプールを導入する理由、それを行う理由は何ですか?スプール以外に複雑なものはありません。必要ないようです。この場合それを取り除く方法、可能な方法は何ですか?


上記の計画は

Microsoft SQL Server 2014(SP2-CU11)(KB4077063)-12.0.5579.0(X64)

14
i-one

オプティマイザがここにスプールを導入する理由、それを行う理由は何ですか?スプール以外に複雑なものはありません。

スプールを超えたものは単純なテーブル参照ではなく、左結合/半結合 alternative が生成されるときに単に複製される可能性があります。

これはlookテーブル(定数スキャン)に少し似ていますが、オプティマイザ*にとっては、VALUESの個別の行のUNION ALLです。句。

追加の複雑さは、オプティマイザがソース行をスプールして再生することを選択するのに十分であり、スプールを後で単純な「テーブル取得」で置き換えることはありません。たとえば、完全結合からの初期変換は次のようになります。

early plan

一般的な変換によって導入された追加のスプールに注意してください。単純なテーブルgetの上のスプールは、ルールSpoolGetToGetによって後でクリーンアップされます。

オプティマイザに対応するSpoolConstGetToConstGetルールがあった場合、原則的には希望どおりに機能します。

この場合それを取り除く方法、可能な方法は何ですか?

実際のテーブル(一時または変数)を使用するか、手動で完全結合からの変換を記述します。次に例を示します。

WITH 
    p([Id], [Code]) AS
    (
        SELECT @id1, 'A'
        UNION ALL
        SELECT @id2, 'B'
    ),
    FullJoin AS
    (
        SELECT
            p.Code,
            d.[Status]
        FROM p
        LEFT JOIN #data d 
            ON d.[Id] = p.[Id]
        UNION ALL
        SELECT
            NULL,
            D.[Status]
        FROM #data AS D
        WHERE NOT EXISTS
        (
            SELECT *
            FROM p
            WHERE p.Id = D.Id
        )
    )
SELECT
    COALESCE(FullJoin.Code, 'X') AS Code,
    COALESCE(FullJoin.Status, 0) AS [Status]
FROM FullJoin;

手動書き換えの計画:

Manual rewrite plan

これは、元の0.0203412ユニットと比較して、0.0067201ユニットの推定コストです。


*Converted TreeLogOp_UnionAllとして観察できます(TF 8605)。 入力ツリー(TF 8606)では、LogOp_ConstTableGetです。 Converted Treeは、解析、正規化、代数化、バインディング、およびその他の準備作業の後のオプティマイザー式要素のツリーを示します。 入力ツリーは、否定正規形への変換(NNF変換)、ランタイム定数の縮小、およびその他のいくつかのビットとボブの後の要素を示しています。 NNF変換には、とりわけ、論理和集合と共通テーブル取得を折りたたむロジックが含まれています。

19
Paul White 9

テーブルスプールは、VALUES句にある2つのタプルセットからテーブルを作成するだけです。

次のように、これらの値を一時テーブルに最初に挿入することで、スプールを削除できます。

DROP TABLE IF EXISTS #data;
CREATE TABLE #data ([Id] int, [Status] int);

INSERT INTO #data
VALUES (100, 1), (101, 2), (102, 3), (103, 2);

DROP TABLE IF EXISTS #p;
CREATE TABLE #p
(
    Id int NOT NULL
    , Code char(1) NOT NULL
);

DECLARE @id1 int = 101, @id2 int = 105;

INSERT INTO #p (Id, Code)
VALUES
        (@id1, 'A'),
        (@id2, 'B');


SELECT
    COALESCE(p.[Code], 'X') AS [Code],
    COALESCE(d.[Status], 0) AS [Status]
FROM #p p
    FULL JOIN #data d ON d.[Id] = p.[Id];

クエリの実行プランを見ると、出力リストにUnionプレフィックスを使用する2つの列が含まれていることがわかります。これは、スプールがunionされたソースからテーブルを作成しているというヒントです。

enter image description here

FULL OUTER JOINでは、SQL Serverがpの値に2回アクセスする必要があります。1回は結合の「サイド」ごとに1回です。スプールを作成すると、結果の内部ループ結合がスプールされたデータにアクセスできるようになります。

興味深いことに、FULL OUTER JOINLEFT JOINRIGHT JOINに置き換え、結果をUNIONに置き換えると、SQL Serverはスプールを使用しません。

SELECT
    COALESCE(p.[Code], 'X') AS [Code],
    COALESCE(d.[Status], 0) AS [Status]
FROM (VALUES
        (101, 'A'),
        (105, 'B')
    ) p([Id], [Code])
    LEFT JOIN #data d ON d.[Id] = p.[Id]
UNION
SELECT
    COALESCE(p.[Code], 'X') AS [Code],
    COALESCE(d.[Status], 0) AS [Status]
FROM (VALUES
        (101, 'A'),
        (105, 'B')
    ) p([Id], [Code])
    RIGHT JOIN #data d ON d.[Id] = p.[Id];

enter image description here

上記のUNIONクエリの使用はお勧めしません。入力のセットが大きい場合は、すでに持っている単純なFULL OUTER JOINより効率的ではない場合があります。

3
Max Vernon