sql >> データベース >  >> RDS >> Database

フィルター処理されたインデックスとINCLUDEd列

    フィルター処理されたインデックスは驚くほど強力ですが、特にフィルターで使用される列や、フィルターを強化したいときに何が起こるかについて、まだ混乱が見られます。

    dba.stackexchangeに関する最近の質問では、フィルター処理されたインデックスのフィルターで使用される列をインデックスの「含まれる」列に含める必要がある理由についてのヘルプが求められました。すばらしい質問です。ただし、これらの列はインデックスに含める必要がないため、前提が不十分であると感じた点が異なります。 。はい、彼らは助けになりますが、質問が示唆しているようには見えませんでした。

    質問自体を確認する手間を省くために、簡単な要約を示します。

    この質問を満たすには…

    SELECT Id, DisplayName 
FROM Users 
WHERE Reputation > 400000;

    …次のフィルタリングされたインデックスはかなり良いです:

    CREATE UNIQUE NONCLUSTERED INDEX Users_400k_Club
ON dbo.Users ( DisplayName, Id )
INCLUDE ( Reputation )
WHERE Reputation > 400000;

    ただし、このインデックスが設定されているにもかかわらず、フィルタされた値がたとえば450000に引き締められている場合、クエリオプティマイザは次のインデックスを推奨します。

    CREATE NONCLUSTERED INDEX IndexThatWasMissing
ON dbo.Users ( Reputation )
INCLUDE ( DisplayName, Id );

    ここで少し言い換えると、この状況を参照することから始めて、別の例を作成しますが、考え方は同じです。別のテーブルを使用して、物事をもっと複雑にしたくありませんでした。

    ポイントは– QOによって提案されたインデックスは元のインデックスですが、その頭をオンにしました。元のインデックスでは、INCLUDEリストにレピュテーションがあり、キー列にDisplayNameとIdがありましたが、新しい推奨インデックスは、キー列にレピュテーションがあり、INCLUDEにDisplayName&IDがあります。理由を調べてみましょう。

    この質問は、Erik Darlingによる投稿に言及しており、ReputationをINCLUDE列に入力することで、上記の「450,000」クエリを調整したと説明しています。 Erikは、INCLUDEリストにレピュテーションがない場合、レピュテーションのより高い値にフィルタリングするクエリは、ルックアップを実行する必要があるか(悪い!)、またはフィルタリングされたインデックスを完全に放棄する必要があることを示しています(潜在的にさらに悪い)。彼は、INCLUDEリストにReputation列があると、SQLに統計が含まれるため、より適切な選択ができると結論付け、INCLUDEのReputationを使用すると、より高いレピュテーション値ですべてフィルター処理されるさまざまなクエリが、フィルター処理されたインデックスをすべてスキャンすることを示します。

    dba.stackexchangeの質問への回答で、Brent Ozarは、Erikの改善はスキャンを引き起こすため、それほど大きくはないと指摘しています。それ自体が興味深い点であり、多少間違っているので、ここに戻ります。

    まず、インデックス全般について少し考えてみましょう。

    インデックスは、データのセットに順序付けられた構造を提供します。 (私は衒学者であり、インデックスのデータを最初から最後まで読むと、一見無計画にページからページへとジャンプする可能性があることを指摘できますが、それでもページを読んでいる間、1つのページから次に、データが順序付けられていることを確認できます。各ページ内で、データを順番に読み取るためにジャンプすることもできますが、ページのどの部分(スロット)をどの順序で読み取る必要があるかを示すリストがあります。私がコメントしない場合にコメントする同じように衒学者に答える以外は、私の衒学者には意味がありません。)

    そして、この順序は主要な列に従っています–それは誰もが簡単に得られるビットです。後でデータを並べ替えるのを避けることができるだけでなく、それらの列によって特定の行または行の範囲をすばやく見つけることができるのにも役立ちます。

    インデックスのリーフレベルには、INCLUDEリストの任意の列の値が含まれます。クラスター化インデックスの場合は、テーブル内のすべての列の値が含まれます(永続化されていない計算列を除く)。インデックスの他のレベルには、キー列と(インデックスが一意でない場合)行の一意のアドレスのみが含まれます。これは、クラスター化インデックスのキーのいずれかです(クラスター化インデックスが一意でない場合は、行の一意化子も含まれます)。 )またはヒープのRowID値。これにより、行の他のすべての列値に簡単にアクセスできます。リーフレベルには、すべての「アドレス」情報も含まれます。

    しかし、それはこの投稿にとって興味深い部分ではありません。この投稿の興味深い点は、「データセットに対して」という意味です。 「インデックスは、データセットに順序付けられた構造を提供する」と言ったことを思い出してください。 "。

    クラスタ化されたインデックスでは、そのデータセットはテーブル全体ですが、別のものである可能性があります。ほとんどの非クラスター化インデックスがテーブルのすべての列に含まれていないことは、おそらくすでに想像できます。これは、非クラスター化インデックスを非常に便利にするものの1つです。これは、通常、基になるテーブルよりもはるかに小さいためです。

    インデックス付きビューの場合、データのセットは、多くのテーブル間の結合を含む、クエリ全体の結果である可能性があります。それは別の投稿です。

    ただし、フィルター処理されたインデックスでは、列のサブセットのコピーだけでなく、行のサブセットでもあります。したがって、この例では、インデックスは40万人以上の評判を持つユーザーのみに適用されます。

    CREATE UNIQUE NONCLUSTERED INDEX Users_400k_Club_NoInclude
ON dbo.Users ( DisplayName, Id )
WHERE Reputation > 400000;

    このインデックスは、40万を超えるレピュテーションを持つユーザーを取得し、DisplayNameとIdで並べ替えます。 (おそらく)Id列はすでに一意であるため、一意である可能性があります。自分のテーブルで似たようなことをしようとする場合は、それに注意する必要があるかもしれません。

    ただし、この時点では、インデックスは各ユーザーのレピュテーションが何であるかを気にせず、レピュテーションがインデックスに含まれるのに十分高いかどうかだけを気にします。ユーザーのレピュテーションが更新され、ユーザーのDisplayNameとIdがインデックスに挿入されるしきい値を超えた場合。下に落ちると、インデックスから削除されます。ハイローラー用に別のテーブルを用意するのと同じですが、基になるテーブルのレピュテーション値を400kのしきい値を超えて増やすことで、そのテーブルにユーザーを追加します。これは、レピュテーション値自体を実際に保存しなくても実行できます。

    したがって、しきい値が45万を超える人を見つけたい場合、そのインデックスにはいくつかの情報がありません。

    確かに、私たちが見つけるすべての人がそのインデックスに含まれていると自信を持って言うことができますが、インデックス自体には、レピュテーションでさらにフィルタリングするのに十分な情報が含まれていません。 1990年代のアカデミー作品賞を受賞した映画のアルファベット順のリスト(アメリカンビューティー、ブレイブハート、ダンスウィズオオカミ、イングリッシュペイシェント、フォレストガンプ、シンドラーのリスト、シェイクスピアインラブ、子羊の沈黙、タイタニック、許されざる者) 、それなら、1994年から1996年の受賞者はそれらのサブセットになると確信できますが、最初にいくつかの情報を入手せずに質問に答えることはできません。

    明らかに、フィルター処理されたインデックスは、年を含めるとさらに便利になります。新しいクエリでは1994〜1996年のものを検索するため、年が重要な列である場合はさらに便利になります。しかし、私はおそらく、1990年代のすべての映画をアルファベット順に一覧表示するクエリに基づいてこのインデックスを設計しました。このクエリは、実際の年が何であるかを気にせず、1990年代かどうかだけを考慮し、年を返す必要はありません。タイトルだけを返すので、フィルタリングされたインデックスをスキャンして結果を取得できます。そのクエリでは、結果を並べ替えたり、開始点を見つけたりする必要はありません。私のインデックスは本当に完璧です。

    フィルタ内の列の値を気にしないより実用的な例は、次のようなステータスです。

    WHERE IsActive = 1

    行が「アクティブ」でなくなると、あるテーブルから別のテーブルにデータを移動するコードをよく目にします。人々は古い行がテーブルを乱雑にすることを望まず、「ホット」データはすべてのデータのごく一部にすぎないことを認識しています。そのため、冷却データをアーカイブテーブルに移動し、アクティブテーブルを小さく保ちます。

    フィルタリングされたインデックスはあなたのためにこれを行うことができます。舞台裏。行を更新し、そのIsActive列を1以外のものに変更するとすぐに、ほとんどのインデックスにアクティブデータがあることだけを気にする場合は、フィルター処理されたインデックスが理想的です。 IsActive値が1に戻った場合でも、行がインデックスに戻されます。

    ただし、これを実現するためにIsActiveをINCLUDEリストに入れる必要はありません。なぜ値を保存したいのですか?値が何であるかはすでにわかっています-それは1です!値を返すように求めているのでない限り、それは必要ありません。そして、答えが1であることがすでにわかっているのに、なぜ値を返すのでしょうか。イライラすることを除いて、エリックが彼の投稿で参照している統計は、INCLUDEリストにあることを利用します。クエリには必要ありませんが、統計に含める必要があります。

    インデックスの有用性を理解するためにクエリオプティマイザが何をする必要があるかを考えてみましょう。

    多くのことを実行する前に、インデックスが候補であるかどうかを検討する必要があります。必要となる可能性のあるすべての行がない場合は、インデックスを使用しても意味がありません。残りを取得する効果的な方法がない限り、そうではありません。 1985年から1995年の映画が欲しいのなら、1990年代の映画の私のインデックスはかなり無意味です。しかし、1994年から1996年の間は、悪くないかもしれません。

    この時点で、他のインデックスの考慮事項と同様に、データを見つけて、残りのクエリの実行に役立つ順序にするのに十分役立つかどうかを考える必要があります(おそらく、マージ結合、ストリーム集約、満足のいくもの) ORDER BY、またはその他のさまざまな理由)。クエリフィルターがインデックスフィルターと完全に一致する場合は、それ以上フィルターをかける必要はありません。インデックスを使用するだけで十分です。これは素晴らしいように聞こえますが、正確に一致しない場合、クエリフィルターがインデックスフィルターよりも厳密である場合(1994-1996の例やErikの450,000のように)、これらの年の値または評判の値が必要になりますチェックする–うまくいけば、リーフレベルのINCLUDEdまたはキー列のどこかからそれらを取得します。それらがインデックスにない場合は、フィルタリングされたインデックスのすべての行に対してルックアップを実行する必要があります(理想的には、ルックアップが呼び出される回数についてのアイデアがあります。これは、Erikが必要とする統計です。含まれている列)。

    理想的には、使用する予定のインデックスは(キーを介して)正しく順序付けられ、返す必要のあるすべての列が含まれ、必要な行だけに事前にフィルター処理されます。それは完璧なインデックスであり、私の実行計画はスキャンになります。

    そうです、SCAN。シークではなく、スキャン。インデックスの最初のページから開始し、必要な数になるまで、または返される行がなくなるまで、行を表示し続けます。スキップせず、並べ替えもせず、行を順番に並べてください。

    シークは、インデックス全体が必要ないことを示唆します。つまり、インデックスのその部分を維持するためにリソースを浪費していることを意味します。クエリを実行するには、開始点を見つけて、行をチェックし続けて、終わりを打つかどうか。スキャンに述語がある場合は、確かに、必要以上のデータを調べてテストする必要がありますが、インデックスフィルターが完全であれば、クエリオプティマイザーはそれを認識し、これらのチェックを実行する必要はありません。 。

    最終的な考え

    INCLUDEは、フィルター処理されたインデックスにとって重要ではありません。これらは、クエリに役立つ可能性のある列への簡単なアクセスを提供するのに役立ちます。フィルターに記載されているかどうかに関係なく、フィルター処理されたインデックスの内容を任意の列で厳密にする場合は、その列をミックス。しかし、その時点で、インデックスのフィルターが正しいかどうか、INCLUDEリストに他に何を含めるべきか、さらにはキー列をどのようにするべきかを尋ねる必要があります。 Erikのクエリは、フィルターの列について言及していても、インデックスにない情報が必要だったため、うまく機能していませんでした。彼は統計の良い使い方も見つけました。そのため、フィルター列を含めることをお勧めします。ただし、それらをINCLUDEに入れると、フィルター処理されているかどうかに関係なく、インデックスが機能しないため、突然シークを開始することはできません。

    読者の皆さんには、フィルター処理されたインデックスを本当によく理解していただきたいと思います。それらは非常に便利であり、自分の権利でテーブルのようにそれらを描き始めると、全体的なデータベース設計の一部になることができます。また、ANSI_NULLsとQUOTED_IDENTIFIERの設定を常に使用する理由でもあります。これらの設定がオンになっていないと、フィルター処理されたインデックスからエラーが発生するためですが、とにかく常にオンになっていることを確認してください。

    ああ、それらの映画はフォレストガンプ、ブレイブハート、イングリッシュペイシェントでした。

    @rob_farley


    1. 列データ型のBYTEとCHARの違い

    2. sqlgroupbyとdistinct

    3. PostgreSQLクエリでラージオブジェクトのサイズを取得しますか?

    4. SQLServerデータベースですべてのチェック制約を有効にする方法-SQLServer/TSQLチュートリアルパート88