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


14

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

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行がスプールされていることです(赤いボックスの実行計画の領域を参照)。

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

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


上記の計画は

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


feedback.azure.comでの関連提案
i-one

回答:


19

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

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

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

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

初期計画

一般的な変換によって導入された余分なスプールに注意してください。単純なテーブル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;

手動書き換えの計画:

手動書き換え計画

これは、オリジナルの0.0203412ユニットと比較して、0.0067201ユニットの推定コストがあります。


*これは、次のように観察することが可能LogOp_UnionAll変換されたツリー(TF 8605)。では、入力ツリー(TF 8606)それがありますLogOp_ConstTableGet変換されたツリーは、構文解析、正規化、algebrization、結合、および他のいくつかの準備作業の後、オプティマイザの発現要素のツリーを示しています。入力ツリー否定標準形(NNFコンバート)、崩壊実行時定数、およびいくつかの他の小物類への変換後の要素を示しています。NNF変換には、論理ユニオンと共通テーブル取得などを折りたたむロジックが含まれています。


3

テーブルスプールは、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'dソースからテーブルを作成しているというヒントです。

ここに画像の説明を入力してください

FULL OUTER JOIN値にアクセスするためにSQL Serverが必要ですp参加の各「側」のために1回、2回。スプールを作成すると、結果の内部ループ結合がスプールされたデータにアクセスできます。

興味深いことに、FULL OUTER JOINをa LEFT JOINとa RIGHT 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];

ここに画像の説明を入力してください

UNION上記のクエリを使用することは推奨していません。より大きな入力セットの場合、FULL OUTER JOIN既にある単純なものよりも効率的ではないかもしれません。


実際のワークロードでは、スプールは本当にそれほど高価ですか?
マックスヴァーノン
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.