実行速度の遅いクエリの実行プランを見ると、一部のノードがインデックスシークで、一部のノードがインデックススキャンであることがわかりました。
インデックスシークとインデックススキャンの違いは何ですか?
どちらが優れていますか?
SQLはどのようにして一方を選択しますか?
これは3つの質問だと思いますが、最初の質問に答えると他の質問も説明できると思います。
ショートバージョン:シークははるかに優れています
短いバージョン:シークは一般的にはるかに優れていますが、非常に多くのシーク(たとえば、厄介な相関サブクエリを使用した不適切なクエリデザイン、またはカーソル操作やその他のループで多くのクエリを実行しているため)は、特にクエリが影響を受けるテーブルのほとんどの行からデータを返す可能性がある場合は、スキャンします。
データ検索操作のファミリ全体をカバーして、パフォーマンスへの影響を完全に理解するのに役立ちます。
テーブルスキャン:クエリに関連するインデックスがまったくない場合、プランナはテーブルスキャンを使用するように強制され、すべての行が参照されることを意味します。これにより、テーブルのデータに関連するすべてのページがディスクから読み込まれ、多くの場合最悪のケースになります。一部のクエリでは、有用なインデックスが存在する場合でもテーブルスキャンを使用することに注意してください。これは通常、テーブル内のデータが小さすぎてインデックスをトラバースするのが面倒だからです(これが当てはまる場合、インデックスの選択性測定が適切であると仮定して、データの増加に応じて変更することを計画します)。
行ルックアップによるインデックススキャン:シークに直接使用できるインデックスが見つからないが、正しい列を含むインデックスが存在する場合、インデックススキャンを使用できます。たとえば、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]、クラスター化インデックスis個別のヒープ構造ではなくテーブルなので、[2] 最大のゲインを得るために、慎重に配置する場所を選択しました。
また、テーブルの「クラスタリングキー」がテーブルのすべての非クラスタ化インデックスに含まれているため、クラスタ化インデックスも注意してください。そのため、ワイドクラスタ化インデックスは一般に良い考えではありません。
[1]実際、can表のevery列をカバーまたは含む非クラスター化インデックスを定義することで、複数のクラスター化インデックスを効果的に作成できますが、これは無駄になる可能性がありますスペースは書き込みパフォーマンスに影響を与えるため、それを行うことを検討する場合は、本当にそうする必要があることを確認してください。
[2]「クラスター化インデックスを使用する場合」と言うときは、一般に、各テーブルに1つずつdoすることをお勧めします。すべての経験則と同様に例外があり、最も一般的なカウンターの例は、一括挿入と順序付けされていない読み取り(おそらくETLプロセスのステージングテーブル)以外のほとんどないテーブルです。
追加ポイント:不完全なスキャン:
残りのクエリによっては、テーブル/インデックススキャンが実際にテーブル全体をスキャンしない場合があることに注意することが重要です。ロジックでクエリプランが許可されている場合、テーブルを早期に中止できる可能性があります。この最も簡単な例はSELECT TOP(1) * FROM HugeTable
です。そのクエリプランを見ると、スキャンから1行のみが返され、IO =統計(SET STATISTICS IO ON; SELECT TOP(1) * FROM HugeTable
)非常に少数のページ(おそらく1ページ)しか読み取らないことがわかります。
WHERE
または_JOIN ... ON
_句の述語を、データの場合はソースであるスキャンと同時に実行できる場合も同じことが起こります。クエリプランナー/ランナーは、この方法でスキャンを早期に終了できるように述語をデータソースにプッシュバックすることについて非常に賢い場合があります(そしてyoは、クエリを再編成してそれを行うのに役立つ場合があります! )。 dataは、標準のクエリプラン表示の矢印に従って右から左に流れますが、logicは左から右に実行され、各ステップ(right-to-左)は、次が開始する前に完了するまで実行されるとは限りません。上記の簡単な例では、クエリプランの各ブロックをエージェントとして見ると、SELECT
エージェントはTOP
エージェントに行を要求し、次に_TABLE SCAN
_エージェントに次に、SELECT
エージェントは別のエージェントを要求しますが、TOP
エージェントは、テーブルリーダーを要求する必要さえないことを知っているため、SELECT
エージェントは「これ以上関連性のない」応答であり、すべての作業が完了したことを知っています。もちろん、多くの操作はこの種の最適化をブロックするため、より複雑な例では、テーブル/インデックススキャンが実際にdoesすべての行を読み取りますが、スキャンが高価な操作である必要があるという結論にジャンプしないように注意してください。
一般的に、シークは良好で、スキャンは不良です。
シークは、クエリがインデックスを効果的に使用し、それを使用して必要な行を見つけることができる場所です。
スキャンでは、クエリがインデックス全体を調べて、必要なものを見つけようとします。
SQLはどのように選択しますか?クエリオプティマイザーの内部では、クエリと使用可能なインデックス、およびそれらのインデックスに関連付けられた統計情報に基づいて決定が行われます。
ここで興味深いかもしれないいくつかの本を読んでください-両方のRed-Gate書店 http://www.red-gate.com/community/books/ から
話題を掘り下げたい場合、(少なくとも私にとって)非常に役立つ本は、RedGate here から無料で入手できるGrant FritcheyによるSQL Server実行プランです。
次のようなクエリがある場合
SELECT *
FROM myTable
SQL Serverは必要な結果を表示するためにすべての行を通過する必要があるため、おそらくインデックススキャンを使用します。
それどころか、
SELECT *
FROM myTable
WHERE myID = 1
確かにインデックスシークになります。 SQL ServerはmyIDインデックスの Bツリー構造 を使用し、適切な行の取得がはるかに高速になります。
他の人は、シークとスキャンの違いを十分に定義しています。この場合、クエリ自体と実行プランナーは、各部分でクエリの述語(フィルター)として使用されている値を確認するために必要な情報を提供する必要があります。通常は、外部キーに常に非クラスター化インデックスを追加することをお勧めします。プログラムコードの使用例によっては、追加の複数列インデックスまたは含まれる列インデックスの作成も検討することをお勧めします。ここに提示されている用語を使用すると、グーグル検索でそれぞれの例について適切な結果が得られます。
しかし、例として、コードが特定のフィルターで列Aと列Bをクエリしているが、列Cと列Eの値も返したい場合、INCLUDEを使用して列Aと列Bにインデックスを作成するとします。列CとEを含むオプション。同じ行の他の値(CとE)を取得するためにルックアップを実行する必要がないので、単一のインデックスシークで必要なすべてが返されます。