実行プランはINDEXを使用せず、テーブルスキャンを使用します


9

インデックスまたはテーブルスキャンを使用する場合、SQL Serverは統計を使用してどちらが優れているかを確認します。

2,000万行のテーブルがあります。(SnapshotKey、Measure)のインデックスと次のクエリがあります。

select Measure, SnapshotKey, MeasureBand
from t1
where Measure = 'FinanceFICOScore'
group by Measure, SnapshotKey, MeasureBand

クエリは500k行を返します。したがって、クエリはテーブルの行の2.5%のみを選択します。

問題は、SQL Serverが私が持っている非クラスター化インデックスを使用せず、代わりにテーブルスキャンを使用する理由です。

統計を更新しました。

ただし、クエリのパフォーマンスは良好です。

テーブルスキャン

テーブルスキャン

強制インデックス

力指数

テーブル/インデックス構造

CREATE TABLE [t1](
    [SnapshotKey] [int] NOT NULL,
    [SnapshotDt] [date] NOT NULL,
    [Measure] [nvarchar](30) NOT NULL,
    [MeasureBand] [nvarchar](30) NOT NULL,
    -- and many more fields
) ON [PRIMARY]

データウェアハウスであるため、テーブルにPKはありません。

CREATE NONCLUSTERED INDEX [nci_SnapshotKeyMeasure] ON [t1]
(
    [SnapshotKey] ASC,
    [Measure] ASC
)

回答:


16

多くの行を返す場合や、行が非常に広い場合は、インデックスシークが最良の選択ではない場合があります。インデックスがカバーしていない場合、ルックアップは高価になる可能性があります。ここ#2を参照してください

このシナリオでは、クエリオプティマイザーは、50,000回の個別ルックアップを実行すると、単一のスキャンよりもコストがかかると推定しています。スキャンとシークの間のオプティマイザの選択(クエリで必要な列のRIDルックアップを使用するが、非クラスタ化インデックスには存在しない)は、各選択肢の推定コストに基づいています。

オプティマイザは常に、検討する最低コストの選択肢を選択します。あなたが見れば推定サブツリーコスト 2つの実行計画のルートノード内のプロパティには、スキャン計画は、計画を求めるよりも低い推定コストを持っていることがわかります。その結果、オプティマイザはスキャンを選択しました。それは本質的にあなたの質問への答えです。

現在、オプティマイザが使用するコストモデルは、システムのパフォーマンス特性と一致する可能性が非常に低い仮定と「マジックナンバー」に基づいています。特に、モデルで行われた1つの仮定は、必要なデータまたはインデックスページが既にメモリにない状態でクエリが実行を開始することです。もう1つは、シーケンシャルI / O(スキャンに期待される)が、RIDルックアップで想定されるランダムI / Oパターンよりも安価であることです。他にも多くのそのような仮定と警告があり、ここで詳細に説明するには多すぎます。

それにもかかわらず、全体としてのコストモデルは、ほとんどのクエリ、ほとんどのデータベーススキーマ、ほとんどのハードウェア構成、ほとんどの場合、あらゆる場所で、一般に「十分な」プランを生成することが示されています。考えてみれば、それはかなりの成果です。

モデルの制限やその他の要因により、オプティマイザが実際にはまったく「十分」ではない計画を選択する場合があります。あなたは「パフォーマンスは良い」と報告しているので、ここではそうではないようです。


9

実際に一致する行は595,947行で、これはデータの約3%です。したがって、ルックアップのコストはすぐに加算されます。テーブルのページごとに100行あるとします。これは、テーブルスキャンで読み取る200,000ページです。これは、595,947回のルックアップよりもはるかに安価です。

GROUP BY質問の節を使用すると、複合キー(Measure、SnapshotKey、MeasureBand)をオンにしたほうがよいでしょう。

「ミッシングインデックス」の提案をご覧ください。ルックアップを回避するために列を含めるように指示します。より一般的には、クエリで他の列を参照する場合、それらはINCLUDE新しいインデックスのキーまたは句にある必要があります。それ以外の場合は、これらの値を取得するために595,947回のルックアップを実行する必要があります。

たとえば、クエリの場合:

select Measure, SnapshotKey, MeasureBand, SUM(NumLoans), SUM(PrinBal)
from t1
where Measure = 'FinanceFICOScore'
group by Measure, SnapshotKey, MeasureBand

...あなたは必要になるでしょう:

CREATE INDEX ixWhatever 
ON t1 (Measure, SnapshotKey, MeasureBand) 
INCLUDE (NumLoans,PrinBal);

6
  1. WHERE条件のフィールドは、インデックスの先行フィールドではありません。

  2. あなたはしているmeasureそうでリテラルの接頭辞NVARCHARのように定義しますNwhere Measure = N'FinanceFICOScore'

でクラスタ化インデックスを作成することを検討してくださいSnapshotKey。一意の場合は、PK(およびクラスター化)にすることができます。一意でない場合は、PKにすることはできませんが、一意でないクラスタ化インデックスにすることはできます。その場合、非クラスター化インデックスはmeasure列にのみ存在します。

また、の最初のフィールドGROUP BYmeasureであることをmeasure考えると、これは主要なフィールドであることからも利益を得ます。

実際、この操作では、代わりににNonClusteredインデックスを定義する必要がある場合があります。Measure, SnapshotKey, MeasureBandこれは、GROUP BY句に一致する正確な順序で行われます。MeasureBand非クラスター化インデックスは既にに基づいておりMeasureMeasureKeyクラスター化インデックスキーになっているため、インデックスにすでに含まれているため、サイズに関しては実際に追加されるだけです(いいえ、Measure非クラスター化インデックスに複製されません)。

@Robは、この問題を解決することは、非クラスタ化インデックスは、この順序でこれらの三つのフィールドを定義することだけが必要であること、そして上のクラスタ化された(非ユニーク)インデックスを作成すると、その彼の答えになりました、削除されたコメントに言及していたSnapshotKeyではありません必要。彼はおそらく正しいです(私はより少ないフィールドが機能することを望んでいました)が、クラスタードインデックスを持つことはこの操作だけでなく、おそらく他のほとんどにとって有益であると私は主張します。


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