インデックスの列の順序はどのくらい重要ですか?


173

インデックス宣言の最初に、最も選択的な列を配置する必要があると聞きました。例:

CREATE NONCLUSTERED INDEX MyINDX on Table1
(
   MostSelective,
   SecondMost,
   Least
)

まず、私が言っていることは正しいですか?その場合、インデックス内の列の順序を並べ替えることでパフォーマンスに大きな違いが見られる可能性がありますか、それとも「すてきな」練習のほうが多いですか?

私が尋ねる理由は、DTAを介してクエリを実行した後、ほとんどすべて同じ列が既存のインデックスと異なるインデックスを作成することをお勧めしたためです。不足している列を既存のインデックスに追加して、それを適切に呼び出すことを検討していました。考え?

回答:


193

次のようなインデックスを見てください。

Cols
  1   2   3
-------------
|   | 1 |   |
| A |---|   |
|   | 2 |   |
|---|---|   |
|   |   |   |
|   | 1 | 9 |
| B |   |   |
|   |---|   |
|   | 2 |   |
|   |---|   |
|   | 3 |   |
|---|---|   |

最初の列が2番目の列を最初に制限するよりも多くの結果を排除するため、最初にAを制限する方法を見てください。1番目の列から2番目の列など、インデックスをどのようにトラバースする必要があるかを想像すると簡単です。最初のパスでほとんどの結果を切り捨てると、2番目のステップがはるかに速くなることがわかります。

別のケースでは、列3でクエリを実行した場合、オプティマイザーはインデックスを使用しません。結果セットを絞り込むのにまったく役に立たないためです。 クエリの実行中はいつでも、次のステップの前に処理する結果の数を絞り込むことで、パフォーマンスが向上します。

インデックスもこの方法で格納されるため、クエリを実行しているときに、インデックス全体で最初の列を見つけるバックトラックはありません。

要するに、いいえ、それはショーのためではなく、実際のパフォーマンス上の利点があります。


13
上の図では、そのインデックスは列1がクエリで指定されている場合にのみ有効であることを覚えておいてください。クエリで結合述語または検索述語の列2のみを指定している場合は、効果がありません。そこで、注文も重要です。多分それは言うまでもありませんが、それを述べたかったです。
CodeCowboyOrg 2015年

3
また、インデックスが上の図のようになっていて、クエリがcolumn1とcolumn2でフィルター処理されていることを覚えておいてください。ただし、column2はより一意であり、実際にフィルター処理したいのは実際にはcolumn2です。列2が最初です。これは直感に反するように見えるかもしれませんが、インデックスは複数のページに格納され、値の範囲を持つツリーであることを覚えておいてください。上記の列1は可能性の1/2を無効にしますが、インデックスは、どのインデックスページに直接進むかをすでに知っています。列2の値。セットを絞り込むために列1は必要ありません。
CodeCowboyOrg 2015年

4
この図は、インデックスがどのように構造化またはナビゲートされるかを正確に表したものではありません。このstackoverflow.com/a/39080819/73226を
Martin Smith、

6
@MartinSmith私はそれが不正確であることに同意しません。それは私の意図したように、非常に単純化されていることは確かです。レベルについてさらに詳しく掘り下げたい場合は、レベルについてさらに詳しく掘り下げて回答してください。木の画像を見ると、私が非常に単純な方法で説明しているものがわかります。これはあまりユニークではなく、SQL固有でもありません。Bツリーインデックスは、非常に多くのものに共通しています。
Nick Craver

@MartinSmithまた、それが不正確であることにも同意しません。あなたが説明しているのは、カバリングインデックスに到達する方法の標準的な動作です-オプティマイザが使用するインデックスページの数を最小限に抑えるため、範囲クエリを実行すると、選択性がはるかに重要になります。スキャンする必要があります。これは、数百万行の大きなテーブルでは重要になる可能性があります
Paul Hatcher

127

列の順序は重要です。どちらの順序が正しいかは、それを照会する方法によって異なります。インデックスを使用して、正確なシークまたは範囲スキャンを実行できます。正確なシークとは、インデックス内のすべての列の値が指定され、クエリが行に正確に当てはまる場合です。シークの場合、列の順序は重要ではありません。範囲スキャンは、一部の列のみが指定されている場合で、この場合は順序が重要になります。SQL Serverは、左端の列が指定されている場合にのみ、次に左端の列が指定されている場合にのみ、範囲スキャンにインデックスを使用できます。あなたは(A、B、C)にインデックスを持っている場合、それはのためのレンジ・スキャンに使用することができA=@aため、A=@a AND B=@bしかしではないためにB=@bのために、C=@cB=@b AND C=@c。ケースA=@a AND C=@cは、A=@a部分はインデックスを使用しますが、使用しC=@cません(クエリはのすべてのB値をスキャンしA=@a、にスキップしませんC=@c)。他のデータベースシステムには、いわゆる「スキップスキャン」演算子があり、外側の列が指定されていない場合に、インデックスの内側の列を利用できます。

その知識があれば、インデックス定義をもう一度見ることができます。オンのインデックスは(MostSelective, SecondMost, Least)MostSelective列が指定されている場合にのみ有効になります。しかし、これが最も選択的であるため、内部列の関連性はすぐに低下します。多くの場合、より良いインデックスがon (MostSelective) include (SecondMost, Least)またはon であることがわかります(MostSelective, SecondMost) include (Least)。内部列は関連性が低いため、インデックス内の適切な位置に選択性の低い列を配置すると、シークのノイズが増えるだけなので、中間ページから移動してリーフページにのみ保持することは理にかなっています。クエリのカバー可能性の目的。つまり、それらをINCLUDEに移動します。これは、Least列のサイズが大きくなるほど重要になります。アイデアは、このインデックスは、MostSelective 正確な値または範囲のいずれかとして、その列が最も選択的であるため、すでに候補行が大幅に制限されています。

一方、インデックス(Least, SecondMost, MostSelective)は間違いに見えるかもしれませんが、実際には非常に強力なインデックスです。Least最外部のクエリとして列があるため、選択性の低い列の結果を集計する必要があるクエリに使用できます。このようなクエリはOLAPおよび分析データウェアハウスで広く使用されており、このようなインデックスが非常に適切な場合に使用されます。このようなインデックスは、関連する行の大きなチャンク(同じ値、通常はある種のカテゴリまたはタイプを示す)の物理レイアウトを編成し、分析クエリを容易にするため、実際に優れたクラスター化インデックスを作成しますLeast

したがって、残念ながら、「正しい」順序はありません。Cookieカッターのレシピに従う必要はありませんが、代わりに、これらのテーブルに対して使用するクエリパターンを分析し、正しいインデックス列の順序を決定します。


3
いつものレムス同様の素晴らしいレスポンス。3番目の段落をもう数回読み、フォローアップします。それがまさに私がしなければならないことかもしれません。
安倍ミースラー2010

「SQL Serverは、左端の列が指定されている場合にのみ、次に左端の列が指定されている場合にのみ、範囲スキャンにインデックスを使用できます。」これは私の理解に欠けていたものです、ありがとう!レンジスキャンが最も右に使用されているインデックス列でのみ実行できることを知りませんでしたが、今ではそれが非常に理にかなっています。
Allon Guralnek 2013

この説明はOracle DBにも当てはまりますか?
1

1
@Roizpiはい、そうです。基本的に、インデックスを持つすべてのリレーションデータベースは同じまたは非常に似た方法で機能しています。
Tatranskymedved 2017年

45

Remusが言うように、それはワークロードに依存します。

私は受け入れられた答えの誤解を招く側面に対処したいと思います。

インデックス内のすべての列に対して等価検索を実行するクエリの場合、大きな違いはありません。

以下は2つのテーブルを作成し、それらに同じデータを入力します。唯一の違いは、1つは最も選択性の高いものから最も選択性の低いものへと順序付けられたキーを持ち、もう1つはその逆です。

CREATE TABLE Table1(MostSelective char(800), SecondMost TINYINT, Least  CHAR(1), Filler CHAR(4000) null);
CREATE TABLE Table2(MostSelective char(800), SecondMost TINYINT, Least  CHAR(1), Filler CHAR(4000) null);

CREATE NONCLUSTERED INDEX MyINDX on Table1(MostSelective,SecondMost,Least);
CREATE NONCLUSTERED INDEX MyINDX2 on Table2(Least,SecondMost,MostSelective);

INSERT INTO Table1 (MostSelective, SecondMost, Least)
output inserted.* into Table2
SELECT TOP 26 REPLICATE(CHAR(number + 65),800), number/5, '~'
FROM master..spt_values
WHERE type = 'P' AND number >= 0
ORDER BY number;

両方のテーブルに対してクエリを実行しています...

SELECT *
FROM   Table1
WHERE  MostSelective = REPLICATE('P', 800)
       AND SecondMost = 3
       AND Least = '~';

SELECT *
FROM   Table2
WHERE  MostSelective = REPLICATE('P', 800)
       AND SecondMost = 3
       AND Least = '~'; 

...どちらもインデックスファインを使用し、まったく同じコストが与えられます。

ここに画像の説明を入力してください

受け入れられた回答のASCIIアートは、実際にはインデックスの構造ではありません。Table1のインデックスページを以下に示します(画像をクリックしてフルサイズで開きます)。

ここに画像の説明を入力してください

インデックスページには、キー全体を含む行が含まれます(この場合、インデックスが一意として宣言されていないため、行識別子に追加のキー列が実際に追加されていますが、これについての詳細はここで確認できます)。

上記のクエリでは、SQL Serverは列の選択性を考慮しません。これは、ルート・ページとすることを発見するのバイナリ検索しキーが (PPP...,3,~ )あると>=(JJJ...,1,~ )し、< (SSS...,3,~ )それはページをお読みください1:118。次に、そのページのキーエントリのバイナリ検索を実行し、移動先のリーフページを見つけます。

選択性の順にインデックスを変更しても、バイナリ検索から予想されるキー比較の数や、インデックスシークを行うためにナビゲートする必要があるページの数には影響しません。せいぜい、キーの比較自体わずかにスピードアップするかもしれません

ただし、最も選択的なインデックスを最初に並べることは、ワークロード内の他のクエリにとっては意味があります。

たとえば、ワークロードに次の両方の形式のクエリが含まれている場合。

SELECT * ... WHERE  MostSelective = 'P'

SELECT * ...WHERE Least = '~'

上記のインデックスは、どちらにも対応していません。MostSelectiveシークとルックアップの価値があるプランを作成するのに十分な選択性がありますが、クエリに対するものでLeastはありません。

ただし、このシナリオ(複合インデックスの先頭列のサブセットでのインデックスシークはカバーされていません)は、インデックスを利用できるクエリの1つのクラスにすぎません。実際にMostSelective単独でMostSelective, SecondMost、または組み合わせて検索することはなく、常に3つの列すべての組み合わせで検索する場合、この理論上の利点は役に立ちません。

逆に次のようなクエリ

SELECT MostSelective,
       SecondMost,
       Least
FROM   Table2
WHERE  Least = '~'
ORDER  BY SecondMost,
          MostSelective 

一般的に規定されたものと逆の順序を持​​つことで助けになります-クエリをカバーするので、シークをサポートし、ブートするために望ましい順序で行を返すことができます。

だから、これはアドバイスのしばしば繰り返される作品ですが、ほとんどで、それはへの潜在的な利点についてのヒューリスティックだのクエリ-そしてそれは実際に見て代わるものではありません、あなたのワークロード。


31

インデックス宣言の最初に、最も選択的な列を配置する必要があります。

正しい。インデックスは、複数の列で構成されるコンポジットにすることができ、左端の原則のため、順序は重要です。理由は、データベースがリストを左から右にチェックし、定義された順序に一致する対応する列参照を見つける必要があるためです。たとえば、列のある住所テーブルにインデックスを作成します。

  • 住所
  • 状態

address列を使用するすべてのクエリはインデックスを利用できますが、クエリに参照cityまたはstate参照のみが含まれる場合、インデックスは使用できません。これは、左端の列が参照されていないためです。クエリのパフォーマンスは、どちらが最適であるかを示します-個々のインデックス、または異なる順序の複数の複合。朗読:The Tipping Point、Kimberley Tripp


使用されなかったのが右端の列だけだった場合はどうなりますか?したがって、クエリでは住所と市を使用しましたが、州は使用しませんでした。その場合、インデックスは使用されますか?
安倍ミースラー2010

@阿部:右端は使用されません-左からインデックスの順序を満たさなければなりません。ミスさん、使えません。
OMGポニー

4
@阿部:住所と市について問い合わせたが、状態については問い合わせなかった場合-はい、インデックスが使用されます。つまり、データベースは、インデックスの左側から開始して、クエリ対象のフィールドを使用して右側に移動できる限り、部分インデックスを使用して要求を満たすことができます。ただし、都市ではなく住所と州を使用してクエリを実行した場合でも、インデックスは使用される可能性がありますが、それほど効率的ではありません。インデックスの住所部分しか使用できないためです(b / c次は市、それはクエリで使用されていません)。
JaredC 2013年

6

他のすべての答えは間違っています。

順序を選択する場合、複合インデックスの個々の列の選択性は重要ではありません

単純な思考プロセスを次に示します。 事実上、インデックスは関連する列の連結です。

その理論的根拠を与えると、唯一の違いは、文字列の最初と最後で異なる2つの「文字列」を比較することです。これは総コストのごく一部です。1つの回答で述べたように、「最初のパス/ 2番目のパス」はありません。

それでは、どの順序を使用する必要がありますか?

  1. でテストした列から開始します。順序=任意です。
  2. 次に、1つの範囲列を追加します。

たとえば、非常に低い選択性の列が最初に来る必要あります。

WHERE deleted = 0  AND  the_datetime > NOW() - INTERVAL 7 DAY
INDEX(deleted, the_datetime)

インデックスの順序を入れ替えると、完全に無視されdeletedます。

(列の順序付けには、さらに多くのルールがあります。)


私が間違っているために反対票はありますか?それとも私は強い意見を持っているからですか?または、他の何か?
リックジェームズ

私の反対票ではありませんでしたが、削除済み= 0は、選択性が低くないように思えますか?テーブルの行の大部分になると思います。
グレッグ

@Greg-これは「選択性が低い」ことを意味すると思います-つまり、使用deletedしても、不要な行を除外するのにはあまり役立ちません。より良い例はありますか?(それは私が答えを書いたときに私の心に浮かんだものです。)
リック・ジェームズ

私の誤解。
グレッグ

1
@ClickOk-ありがとう。私のクックブックはいくつかの基本的な情報を提供します: mysql.rjweb.org/doc.php/index_cookbook_mysql
Rick James
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.