インデックスを作成するときにINCLUDE句を使用する理由


432

70-433試験の勉強中に、次の2つの方法のいずれかでカバーインデックスを作成できることに気付きました。

CREATE INDEX idx1 ON MyTable (Col1, Col2, Col3)

-または-

CREATE INDEX idx1 ON MyTable (Col1) INCLUDE (Col2, Col3)

INCLUDE句は私にとって新しいものです。なぜそれを使用し、INCLUDE句の有無にかかわらずカバリングインデックスを作成するかを決定する際にどのようなガイドラインを提案しますか?

回答:


364

列がではなくWHERE/JOIN/GROUP BY/ORDER BYSELECT句の列リストのみにある場合。

このINCLUDE句は、インデックスツリーではなく、最下位/リーフレベルでデータを追加します。ツリーの一部ではないため、インデックスが小さくなります

INCLUDE columnsインデックスのキー列ではないため、順序付けされていません。これは、前述したように、述語やソートなどにはあまり役に立たないことを意味します。ただし、キー列から数行の残余ルックアップがある場合に役立つことがあります。

効果的な例を含む別のMSDN記事


7
それで、これはカバーされたインデックスのより安価なバージョンを作成するためのテクニックでしょうか?
JMarsch 2012

3
@gbn、この文をさらに詳しく説明してもらえますか。また、include句が並べ替えに役立たないことを意味する理由などを説明してください。 。ツリーの一部ではないため、これによりインデックスが小さくなります。 "
Tola Odejayi 2013年

4
@JMarsch:返信が遅くなって申し訳ありませんが、はい、これはまさにそのとおりです。
gbn 2013年

10
@Tola Odejayi:INCLUDE列はインデックスのキー列ではないため、順序付けされていません。そのため、JOINや並べ替えには一般的に役立ちません。そして、それらはキー列ではないため、キー列のようなBツリー構造全体に含まれません
gbn

4
これは最も受け入れられる答えですが、さらに説明が必要だと思います。一部のクエリで列がの一部でSELECTあり、一部ではない場合はどうなりますか?\
Chisko

215

INCLUDEを使用して1つ以上の列を非クラスター化インデックスのリーフレベルに追加します。そうすることで、クエリを "カバー"できます。

従業員のID、部門ID、姓を照会する必要があるとします。

SELECT EmployeeID, DepartmentID, LastName
FROM Employee
WHERE DepartmentID = 5

(EmployeeID、DepartmentID)に非クラスター化インデックスがある場合、特定の部門の従業員を見つけたら、「ブックマークルックアップ」を実行して実際の完全な従業員レコードを取得する必要があります。 。従業員が多い場合は、パフォーマンスの点でかなり高くつく可能性があります。

その姓をインデックスに含めていた場合:

CREATE NONCLUSTERED INDEX NC_EmpDep 
  ON Employee(EmployeeID, DepartmentID)
  INCLUDE (Lastname)

次に、必要なすべての情報を非クラスター化インデックスのリーフレベルで利用できます。非クラスター化インデックスを検索して、特定の部門の従業員を見つけるだけで、必要な情報がすべて揃い、インデックスで見つかった各従業員のブックマークルックアップは不要になります。多くの時間を節約できます。

明らかに、すべての非クラスター化インデックスにすべての列を含めることはできません。ただし、「カバー」される(そして頻繁に使用される)1つまたは2つの列が欠落しているクエリがある場合、それらを含めると非常に役立ちます。適切な非クラスター化インデックスに。


25
このインデックスを使用してもよろしいですか?なぜ従業員IDなのか?キー列に必要なのはDepartmentIDだけですか?あなたは権威あるものとしてここに引用されています:stackoverflow.com/q/6187904/27535
gbn '31年

3
あなたの説明は良いですが、実際にあなたが概説するユースケースと一致していません。キー列JOINはクエリのフィルターまたはキー上にあるINCLUDE必要があり、sは、取得するが並べ替えではないデータである必要があります。
JNK、

15
まず、インデックスEmployee(EmployeeID、DepartmentID)は、DepartmentID = 5のフィルターに使用されません。その順序が一致しないためです
AnandPhadke

29

この議論は重要な点を見落としている:問題は、「非キー列」がインデックス列として含まれるか、含まれる列として含まれるほうがよいかでない。

問題は、include-mechanismを使用して、実際にはインデックスに必要のない列を含めるのがどれほどコストがかかるかです。(通常、where句の一部ではありませんが、selectに含まれることがよくあります)。したがって、あなたのジレンマは常に:

  1. id1、id2 ... idN のみでインデックスを使用するか、
  2. id1、id2 ... idNにインデックスを使用し col1、col2 ... colNを含める

ここで、id1、id2 ... idNは制限でよく使用される列であり、col1、col2 ... colNは選択されることが多いが、通常制限で使用されない列です。

(これらの列をすべてインデックスキーの一部として含めるオプションは、常にばかげています(制限で使用されている場合を除く)-インデックスを更新して並べ替える必要があるため、メンテナンスが常に高価になるため「キー」は変更されていません)。

オプション1または2を使用しますか?

回答:テーブルがめったに更新されない場合(主に挿入/削除される場合)、include-mechanismを使用して「ホット列」(selectでよく使用されますが、制限ではあまり使用されません)を含めることは比較的安価です。挿入/削除では、とにかくインデックスを更新/ソートする必要があるため、すでにインデックスを更新しているときに、余分な列をいくつか格納することに伴うオーバーヘッドはほとんどありません。オーバーヘッドは、インデックスに冗長な情報を格納するために使用される追加のメモリとCPUです。

付属-列が頻繁に更新される列が追加し検討した場合(index-せずにキー更新中-columns) - または - useオプション1 -インデックスがあなたのテーブルのコピーに近くなると彼らのように多くの場合お勧めします!また、特定のinclude-columnを追加してもパフォーマンスに差がないことが判明した場合は、それらを追加するという考えをスキップすることをお勧めします:)それらが有用であることを確認してください!

キー(id1、id2 ... idN)の同じ値ごとの行の平均数も、いくつか重要な場合があります。

カラムが- インデックスのインクルード -カラムとして追加される- 制限で使用されている場合インデックスが使用できる限り(index- key -columns に対する制限に基づく)、SQL Serverは一致します。テーブル自体を高価な方法で回避するのではなく、インデックス(leaf-node-values)に対する列制限。


18

基本インデックス列はソートされますが、含まれている列はソートされません。これにより、インデックスを維持するためのリソースを節約しながら、クエリをカバーするために含まれる列にデータを提供することが可能になります。したがって、クエリを対象とする場合は、検索条件を指定して、インデックスの並べ替えられた列に行を配置し、その後、検索データ以外の並べ替えられていない追加の列を「含める」ことができます。これは、インデックスのメンテナンスにおけるソートと断片化の量を減らすのに役立ちます。


7

理由(インデックスのリーフレベルのデータを含む)が適切に説明されている理由。これについて2つの揺れを与える理由は、クエリを実行するときに、追加の列が含まれていない場合(SQL 2005の新機能)、SQL Serverはクラスター化インデックスに移動して追加の列を取得する必要があるためです。これには時間がかかり、新しいデータページがメモリに読み込まれると、SQL Serverサービス、ディスク、およびメモリ(具体的にはバッファキャッシュ)への負荷が増加し、バッファキャッシュから頻繁に必要な他のデータを押し出す可能性があります。


実際に使用しているメモリが少ないことを証明する方法はありますか?それも私が期待していることですが、私は仕事でこれについて少し静的になっています
Asken

ヒープまたはクラスター化インデックスからメモリにページをロードする必要があること、およびインデックスページをメモリにロードする必要があることを考えると、重複するデータをメモリに配置することは数学が非常に簡単になります。具体的に測定する方法はありません。
mrdenny、2012年

5

すでに与えられた回答で私が見たことがない追加の考慮事項は、含まれる列がvarchar(max)などのインデックスキー列として許可されないデータ型である可能性があることです。

これにより、そのような列をカバリングインデックスに含めることができます。私は最近、これを行う必要があり、nHibernateが生成したクエリにSELECTに多数の列があり、有用なインデックスが提供されました。


3

INCLUDEキーにその列が必要ない場合にキー列よりも優先する理由の1つは、ドキュメントです。これにより、将来のインデックスの進化がはるかに容易になります。

あなたの例を考えてみましょう:

CREATE INDEX idx1 ON MyTable (Col1) INCLUDE (Col2, Col3)

そのインデックスは、クエリが次のようになっている場合に最適です。

SELECT col2, col3
  FROM MyTable
 WHERE col1 = ...

もちろんINCLUDE、重要な部分に列を追加することで追加のメリットを得られる場合は、列を配置しないでください。次のクエリはどちらも、実際col2にはインデックスのキーの列を優先します。

SELECT col2, col3
  FROM MyTable
 WHERE col1 = ...
   AND col2 = ...
SELECT TOP 1 col2, col3
  FROM MyTable
 WHERE col1 = ...
 ORDER BY col2

のは、これがあると仮定しましょうではない場合、私たちが持っているcol2中でINCLUDE、インデックスの木の部分でそれを持つだけの何のメリットがないため句。

数年早送りします。

このクエリを調整する必要があります。

SELECT TOP 1 col2
  FROM MyTable
 WHERE col1 = ...
 ORDER BY another_col

そのクエリを最適化するには、次のインデックスが最適です。

CREATE INDEX idx1 ON MyTable (Col1, another_col) INCLUDE (Col2)

そのテーブルにあるインデックスを確認すると、以前のインデックスがまだ残っている可能性があります。

CREATE INDEX idx1 ON MyTable (Col1) INCLUDE (Col2, Col3)

今、あなたはそれを知っているCol2し、Col3インデックス・ツリーの一部ではないため、読み取りインデックス範囲を狭めたり、行を注文するために使用されていません。Isはanother_column、インデックスのキー部分の末尾に追加しても安全です(後col1)。何かを壊すリスクはほとんどありません:

DROP INDEX idx1 ON MyTable;
CREATE INDEX idx1 ON MyTable (Col1, another_col) INCLUDE (Col2, Col3);

そのインデックスは大きくなるが、それでもリスクはありますが、新しいインデックスを導入するよりも、既存のインデックスを拡張する方が一般的には適切です。

なしINCLUDEでインデックスを作成する場合、another_col直後に追加することでどのクエリが壊れるのかがわかりませんCol1

CREATE INDEX idx1 ON MyTable (Col1, Col2, Col3)

とのanother_col間に追加するCol1Col2どうなりますか?他のクエリは影響を受けますか?

テーブルからのフェッチを回避するためだけに列を追加する場合は、INCLUDEキー列と比べて他の「利点」があります。しかし、私はドキュメンテーションの側面を最も重要なものと考えています。

あなたの質問に答えるには:

INCLUDE句の有無にかかわらずカバリングインデックスを作成するかどうかを決定する際に、どのガイドラインを提案しますか?

テーブルにアクセスせずにインデックスでその列を使用できるようにするためだけに列をインデックスに追加する場合は、その列をINCLUDE句に挿入します。

列をインデックスキーに追加すると、追加の利点がもたらされる場合(たとえばorder by、読み取りインデックスの範囲を狭める可能性があるため、またはそのため)、キーに追加します。

これについてのより長い議論をここで読むことができます:

https://use-the-index-luke.com/blog/2019-04/include-columns-in-btree-indexes


2

インデックス定義にインライン化されたすべての列の合計サイズには制限があります。とはいえ、これほど広いインデックスを作成する必要はありませんでした。私にとっての大きな利点は、特定の順序で定義する必要がないため、列を含む1つのインデックスでより多くのクエリをカバーできることです。インデックス内のインデックスとして考えてください。1つの例は、StoreID(StoreIDは選択性が低いことを意味し、各店舗が多くの顧客に関連付けられていることを意味します)と顧客の人口統計データ(LastName、FirstName、DOB)です。これらの列をこの順序でインライン化した場合(StoreID、LastName 、FirstName、DOB)の場合、StoreIDとLastNameがわかっている顧客のみを効率的に検索できます。

一方、StoreIDにインデックスを定義し、LastName、FirstName、DOB列を含めると、本質的に、StoreIDで2つのシークインデックス述語を実行し、次に含まれる列のいずれかで述語をシークできます。これにより、StoreIDで始まる限り、可能なすべての検索順列をカバーできます。

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