インデックスシークとインデックススキャン


64

実行速度の遅いクエリの実行プランを見ると、ノードの一部がインデックスシークであり、一部がインデックススキャンであることがわかりました。

インデックスシークとインデックススキャンの違いは何ですか?

どちらがパフォーマンスが良いですか?

SQLはどのように一方を選択しますか?

これは3つの質問ですが、最初の質問に答えると他の質問も説明できると思います。


6
use-the-index-lukeに関する素晴らしいリファレンスがあります
マリアン

7
すべてのスキャンが悪いわけではありません-時にはそれがクエリを満たす最も効率的な方法です。また、すべてのシークがシークであるわけではないことに注意してください-多くの場合、実際には範囲スキャンであり、シークは範囲の始まりに到達した方法のみを示します。
アーロンバートランド

@AaronBertrandですが、範囲の先頭に到達してそれを読み取る場合、基本的にはとにかくデータが必要であることを意味します。また、範囲の終わりを探します。
ジョージPolevoy

回答:


76

ショートバージョン:シークははるかに優れています

短いバージョン:シークは一般的にはるかに優れていますが、シークは非常に多くあります(たとえば、厄介な相関サブクエリを使用した不適切なクエリデザイン、またはカーソル操作や他のループで多くのクエリを作成しているため)。特に、クエリが影響を受けるテーブルのほとんどの行からデータを返す可能性がある場合にスキャンします。

データ検索操作のファミリ全体をカバーして、パフォーマンスへの影響を完全に理解するのに役立ちます。

テーブルスキャン:クエリに関連するインデックスがまったくない場合、プランナーはテーブルスキャンの使用を強制されます。つまり、すべての行が参照されます。これにより、テーブルのデータに関連するすべてのページがディスクから読み取られる可能性があり、これは多くの場合最悪の場合です。いくつかのクエリでは、有用なインデックスが存在する場合でもテーブルスキャンを使用することに注意してください-これは通常、テーブル内のデータが非常に小さいため、インデックスを走査するのが面倒であるためです(この場合は、インデックスの選択性の指標が適切であると仮定して、データが大きくなるにつれて変更する計画を立ててください)。

行ルックアップを使用したインデックススキャン:シークに直接使用できるインデックスは見つかりませんが、適切な列を含むインデックスが存在する場合、インデックススキャンを使用できます。たとえば、column1、col2、col3にインデックスを持つ20列の大きなテーブルがあり、を発行するSELECT col4 FROM exampletable WHERE col2=616場合、この場合、インデックスをスキャンしてクエリするcol2方が、テーブル全体をスキャンするよりも優れています。一致する行が見つかったら、出力(またはさらに結合)のためにデータページをピックアップcol4に読み込む必要があります。これは、クエリプランで表示されるときの "ブックマークルックアップ"ステージです。

行ルックアップを使用しないインデックススキャン:上記の例の場合、SELECT col1, col2, col3 FROM exampletable WHERE col2=616データページの読み取りに余分な労力は必要ありません。インデックス行の一致col2=616が見つかると、要求されたすべてのデータがわかります。このため、検索されない列が表示されることがありますが、出力の要求があり、インデックスの最後に追加される可能性があります。行の検索を保存できます。この理由およびこの理由のみで列をインデックスに追加するINCLUDE場合、これらの列に基づいてクエリを実行するためにインデックスレイアウトを最適化する必要がないことをエンジンに伝える句を追加します(これにより、これらの列に対する更新を高速化できます) 。インデックススキャンは、フィルタリング句のないクエリからも発生する可能性SELECT col2 FROM exampletableがあります。テーブルページの代わりにこのサンプルインデックスをスキャンします。

インデックスシーク(行ルックアップの有無にかかわらず)シークでは、インデックスのすべてが考慮されるわけではありません。クエリの場合SELECT * FROM exampletable WHERE c1 BETWEEN 1234 AND 4567、クエリエンジンは、インデックスでツリーベースの検索を行うことで一致する最初の行を見つけることができ、c1その後、範囲の最後に到達するまでインデックスを順番にナビゲートできます(これはクエリでも同じですというのはc1=1234=操作であっても条件に一致する行が多数存在する可能性があるためです)。これは、インデックス(またはテーブル)内のすべてのページではなく、関連するインデックスページ(および最初の検索に必要ないくつか)だけを読み取る必要があることを意味します。

クラスター化インデックス:クラスター化インデックスでは、テーブルデータは個別のヒープ構造ではなく、そのインデックスのリーフノードに格納されます。これは、必要な列に関係なく、そのインデックスを使用して行を検索した後、余分な行ルックアップが必要ないことをTEXT意味しVARCHAR(MAX)ます(列や長いデータを含む列などのページ外データがない限り)。

あなたは、これだけの理由で1つのクラスタ化インデックスを持つことができます[1] 、クラスタ化インデックスがある代わりに、別のヒープ構造を有するのあなたのテーブル、あなたがいずれかを使用する場合は、[2]あなたは最大ゲインを得るために慎重にそれを置く場所を選びました。

また、クラスタ化インデックスはテーブルの「クラスタ化キー」であり、テーブル上のすべての非クラスタ化インデックスに含まれているため、一般的に、ワイドクラスタ化インデックスはお勧めできません。

[1]実際には、テーブル上のすべての列をカバーまたは含む非クラスター化インデックスを定義することにより、複数のクラスター化インデックスを効果的に作成できます、これはスペースを浪費する可能性があり、書き込みパフォーマンスに影響を与える可能性があります。本当に必要です。

[2]「クラスター化インデックスを使用する場合」と言うとき、一般的に各テーブルに1つを作成することをお勧めします。すべての経験則と同様に、例外があります。バルク挿入と順序なし読み取り(ETLプロセスのステージングテーブル)以外はほとんど見られないテーブルは、最も一般的な反例です。

追加ポイント:不完全なスキャン:

クエリの残りの部分によっては、テーブル/インデックススキャンが実際にテーブル全体をスキャンしない場合があることを覚えておくことが重要です。ロジックでクエリプランが許可されている場合、早期に中止することができます。これの最も簡単な例はSELECT TOP(1) * FROM HugeTable、そのクエリプランを見ると、スキャンから1行のみが返されたSET STATISTICS IO ON; SELECT TOP(1) * FROM HugeTableことがわかります。また、IO統計()を見ると、非常に小さな数しか読み取っていないページの(おそらく1つだけ)。

データのソースであるスキャンと同時にWHEREor JOIN ... ON句の述部を実行できる場合も同じことが起こります。クエリプランナー/ランナーは、この方法でスキャンを早期に終了できるように、述部をデータソースにプッシュすることについて非常に賢い場合があります(そして、そうするのを助けるためにクエリを並べ替えることが賢い場合があります!)。しながら、データが標準クエリプラン表示の矢印に従って右から左へ流れて、ロジックは、左から右へ実行され、次を開始する前に(右から左)の各ステップは、必ずしも完了するまで実行されません。上記の簡単な例では、クエリプランの各ブロックをエージェントとして見ると、エージェントはSELECTエージェントにTOP行を要求し、エージェントは行を要求します。TABLE SCANエージェントはSELECT別のエージェントを要求しますが、別のエージェントを要求しTOPますが、テーブルリーダーに問い合わせる必要がないことをエージェントは認識し、SELECTエージェントは「関連性がなくなりました」という応答を受け取り、すべての作業が完了したことを認識します。多くの操作は、テーブル/インデックス・スキャンは、実際より複雑な例ではそれほど頻繁に当然の最適化のこの種のを阻止すべての行を読みますが、任意のスキャンは高価な操作でなければならないという結論にジャンプしないように注意してください。


6

一般に、シークは良好で、スキャンは不良です。

シークとは、クエリがインデックスを効果的に使用し、それを使用して必要な行を見つけることができる場所です。

スキャンは、クエリが必要なものを見つけようとしてインデックス全体を検索する場所です。

SQLはどのように選択しますか?クエリオプティマイザーの内部では、クエリと使用可能なインデックス、およびそれらのインデックスに関連付けられた統計情報に基づいて決定が行われます。

ここで興味があるかもしれない読むべき本がいくつかあります-http://www.red-gate.com/community/books/の Red-Gate書店からの両方

  • Grant FritcheyによるSQL Server実行計画
  • Benjamin Nevarezによるクエリオプティマイザーの内部
  • Holger SchmelingによるSQL Server統計

7
同じプランの場合、1回のテーブルスキャンは良好で、100万回のシークは不良です。したがって、最初のステートメントは完全に正しいわけではありません。
マリアン

確かに、インデックスシークとインデックススキャンにはそれぞれ独自の用途があり、基礎となるテーブルとクエリのコンテキストがなくても、他のものより優れているとは言えません。ほとんどの場合、テーブルの統計が不正確な場合、実行計画が最適ではない可能性があります。たとえば、インデックススキャンに対してインデックスシークが誤って選択されたり、逆の場合があります。
チャオ

5

主題を掘り下げたい場合、非常に役立つ本(少なくとも私にとって)は、Grant FritcheyによるSQL Server Execution Plansであり、ここでRedGate から無料で入手できます

次のようなクエリがある場合

SELECT *
FROM myTable

SQL Serverは、必要な結果を表示するためにすべての行を調べる必要があるため、インデックススキャンを使用する可能性があります。

それどころか、

SELECT *
FROM myTable
WHERE myID = 1

確かにインデックスシークになります。SQL ServerはmyIDインデックスのBツリー構造を使用し、適切な行の取得がはるかに高速になります。


「確かに」同意するかどうかわからない-インデックスの先頭の列にmyIDがある場合でも、シークは最適な答えではないかもしれません(一意かどうかなど、多くのことに依存します- customersテーブルではtrueですが、ordersテーブルのcustomerIDではなく、カバーする必要があるがインデックスにはない列の数など)。
アーロンバートランド

この答えは、提起された質問を本当に網羅しているとは思わない。
ZERO3

5

他の人は、シークとスキャンの違いを十分に定義しています。この場合、クエリ自体と実行プランナーは、各値がクエリの述語(フィルター)として使用される値を確認するために必要な情報を提供する必要があります。通常、外部キーには常に非クラスター化インデックスを追加することをお勧めします。プログラムコードのユースケースによっては、追加の複数列インデックスまたは含まれる列インデックスの作成を検討することもできます。ここに記載されている用語を使用すると、Google検索でそれぞれの例について適切な結果が得られます。

しかし、例として、コードが特定のフィルターで列Aと列Bを照会しているが、列Cと列Eの値を返す場合、INCLUDEを使用して列AとBにインデックスを作成するとします列CおよびEを含むオプション。同じ方法で他の値(CおよびE)を取得するためにルックアップを行う必要がないため、単一のインデックスシークは必要なすべてを返します。

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