一時テーブルがシークおよびブックマークルックアップを使用しているときに、テーブル変数がインデックススキャンを強制するのはなぜですか?


18

テーブル変数を使用すると、オプティマイザーがインデックスシークを使用してからブックマークルックアップとインデックススキャンを使用できなくなる理由を理解しようとしています。

テーブルにデータを入力する:

CREATE TABLE dbo.Test 
(
    RowKey INT NOT NULL PRIMARY KEY, 
    SecondColumn CHAR(1) NOT NULL DEFAULT 'x',
    ForeignKey INT NOT NULL 
) 

INSERT dbo.Test 
(
    RowKey, 
    ForeignKey
) 
SELECT TOP 1000000 
    ROW_NUMBER() OVER (ORDER BY (SELECT 0)),
    ABS(CHECKSUM(NEWID()) % 10)     
FROM sys.all_objects s1
CROSS JOIN sys.all_objects s2 

CREATE INDEX ix_Test_1 ON dbo.Test (ForeignKey) 

テーブル変数に単一のレコードを入力し、外部キー列を検索して主キーと2番目の列を検索します。

DECLARE @Keys TABLE (RowKey INT NOT NULL) 

INSERT @Keys (RowKey) VALUES (10)

SELECT 
    t.RowKey,
    t.SecondColumn
FROM
    dbo.Test t 
INNER JOIN 
    @Keys k
ON
    t.ForeignKey = k.RowKey

以下は実行計画です。

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

代わりに一時テーブルを使用した同じクエリ:

CREATE TABLE #Keys (RowKey INT NOT NULL) 

INSERT #Keys (RowKey) VALUES (10) 

SELECT 
    t.RowKey,
    t.SecondColumn
FROM
    dbo.Test t 
INNER JOIN 
    #Keys k
ON
    t.ForeignKey = k.RowKey

このクエリプランは、シークとブックマークのルックアップを使用します。

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

オプティマイザが、テーブル変数ではなく、一時テーブルを使用してブックマークを検索するのはなぜですか?

この例では、テーブル変数を使用して、ストアドプロシージャ内のユーザー定義のテーブルタイプから送信されるデータを表します。

外部キーの値が数十万回発生した場合、インデックスシークが適切でない可能性があることを認識しています。その場合、おそらくスキャンがより良い選択でしょう。私が作成したシナリオでは、値が10の行はありませんでした。私はまだ動作が興味深いと考えており、その理由があるかどうかを知りたいと思っています。

SQLフィドル

追加OPTION (RECOMPILE)しても動作は変わりませんでした。UDDTには主キーがあります。

@@VERSION SQL Server 2008 R2(SP2)-10.50.4042.0(X64)(ビルド7601:Service Pack 1)(ハイパーバイザー)

回答:


15

この動作の理由は、RowKeyを先頭列とするインデックスがないため、SQL ServerがForeignKeyに一致する行数を決定できないためです(#tempテーブルの統計からこれを推測できますが、テーブル変数/ UDTTに存在する)、100,000行の推定を行います。これは、シーク+ルックアップよりもスキャンの方が適切に処理されます。行が1行しかないことにSQL Serverが気付く頃には、手遅れです。

UDTTを異なる方法で構築できる場合があります。SQL Serverの最新バージョンでは、テーブル変数にセカンダリインデックスを作成できますが、この構文は2008 R2では使用できません。

ところで、ネストされたループ結合を示唆することによってビットマップ/プローブを回避しようとすると、(少なくとも私の限られたトライアルでは)シーク動作を得ることができます:

DECLARE @Keys TABLE (RowKey INT PRIMARY KEY); -- can't hurt

INSERT @Keys (RowKey) VALUES (10);

SELECT 
     t.RowKey
    ,t.SecondColumn
FROM
    dbo.Test t 
INNER JOIN 
    @Keys k
ON
    t.ForeignKey = k.RowKey
    OPTION (LOOP JOIN);

数年前にポール・ホワイトからこのトリック学びました。もちろん、実動コードにあらゆる種類の結合ヒントを置くことには注意する必要があります-基礎となるオブジェクトに変更を加え、その特定の種類の結合がもはや不可能または最適ではなくなった場合、失敗する可能性があります。

より複雑なクエリの場合、およびSQL Server 2012以降に移行する場合、トレースフラグ2453が役立つ可能性があります。ただし、このフラグはこの単純な結合には役立ちませんでした。そして、同じ免責事項が適用されます-これは、大量のドキュメントと厳格な回帰テスト手順を実施せずに一般的に行うべきではない単なる代替方法です。

また、Service Pack 1は長い間サポート対象外です。ServicePack 3 + MS15-058を入手する必要があります。


3

テーブル変数と一時テーブルは、さまざまな方法で異なる方法で処理されます。あり、ここで素晴らしい答え、彼らが異なる場合についての詳細の多くが付いているが。

具体的には、一時テーブルには追加の統計が生成され、並列プランがありますが、テーブル変数には統計が制限され(列レベルの統計はありません)、並列プランがないという事実が原因です。

ストアドプロシージャの実行中は、テーブル変数を一時テーブルにダンプする方が良いでしょう。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.