web-dev-qa-db-ja.com

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

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

テーブルへの入力:

_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
_

以下は実行計画です。

enter image description here

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

_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
_

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

enter image description here

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

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

外部キー値が数十万回発生した場合、インデックスシークが適切でない可能性があることを理解しています。その場合は、スキャンの方が適しています。私が作成したシナリオでは、値が10の行はありませんでした。それでもこの動作は興味深いものであり、その理由があるかどうか知りたいと思います。

SQLフィドル

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

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

18
8kb

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

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以降に移行すると、 トレースフラグ245 が役立つ可能性があります。ただし、このフラグはこの単純な結合には役立ちませんでした。また、同じ免責事項が適用されます。これは、大量のドキュメントと厳密な回帰テスト手順を実施していなければ、一般的に行うべきではない代替策にすぎません。

また、Service Pack 1はサポート対象外となっており、 Service Pack + MS15-058 でご利用いただけます。

15
Aaron Bertrand

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

特にあなたのケースでは、テーブル変数がより限定された統計(列レベルの統計なし)を持ち、並列プランがないことが原因であるのに対し、一時テーブルには追加の統計および並列プランが含まれる可能性があると思います。

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

3
Kenneth Fisher