インデックスをフィルターされた(非null値)インデックスに置き換えるとどのような影響がありますか?


10

私たちのプロジェクトは、非常に大規模で非常に複雑なデータベースを実行しています。そのため、約1か月前に、null値を含むインデックス付き列で使用される領域が大きくなりすぎていることに気付きました。これに対する応答として、1%を超えるnull値を含むすべての単一列インデックスを動的に検索し、値がNOT NULLであるという条件でフィルター処理されたインデックスとしてそれらのインデックスを削除して再作成するスクリプトとして書きました。これにより、データベース全体で数百のインデックスが削除および再作成され、通常、DB全体で使用されるスペースのほぼ15%が解放されます。

今私はこれについて2つの質問があります:

A)この方法でフィルター処理されたインデックスを使用することの欠点は何ですか?パフォーマンスが向上するだけだと思いますが、パフォーマンス上のリスクはありますか?

B)インデックスを削除して再作成すると、「インデックスXYZは削除できないため、削除できません」というエラーが発生しました。後でチェックすると、すべてが期待どおりに実行されました。これはどうして起こりますか?

助けてくれてありがとう!

編集: @Thomas Kejserへの返信

こんにちは、ありがとうございます。しかし、これは災害でした。当時、私たちは次のようないくつかのことを理解していませんでした:

  1. SQLOSはクエリ中に、テーブル列の結合にNULL値を使用できないと判断する前に、インデックスプランを作成します。つまり、クエリで使用されるすべてのフィルター処理されたインデックスのインデックスをフィッティングするWHERE句フィルターが本当に必要です。そうしないと、インデックスはまったく使用されません。
  2. インデックスを削除して作成し、その後統計を冗長に更新するだけでは、更新された計画を作成するには不十分である可能性があります。場合によっては、SQL Serverが計画の再評価を余儀なくされるほど高いワークロードのみが表示されることがあります。
  3. 常識とロジックだけで判断するのが難しい実行プランナーの機能には、いくつかの珍しいものがあります。数千のコードビハインドで生成されたさまざまなクエリのバリエーションでさえ、一見役に立たないように見えるインデックスは、重要なクエリで使用される統計やクエリプランに役立ちます。

最終的に、これらの変更は元に戻されました。したがって、フィルター選択されたインデックスは強力なツールですが、これらの列からフェッチされるデータを正確に理解する必要があります。スペースの問題は別として、通常のインデックスは適用がかなり簡単ですが、フィルター選択されたインデックスは非常にカスタマイズされたソリューションを表します。これらは通常のインデックスの代わりではなく、必要な特別な状況での拡張です。


インデックス作成戦略を再検討することもできます。数百の単一フィールドインデックスがある場合、それはおそらく最適ではありません。
JNK

これらの必要性は、データベースが別のシステムから部分的に継承されているという事実から来ています。デフォルトでは、いくつかの抽象テーブルと、まったく使用されない可能性があるいくつかの抽象列があり、これらの大量のインデックス付きNULL値のほとんどが生成されます。単一フィールドインデックスについては、各外部キーにインデックスを付ける必要があるという基本要件から作成され、それらの多くは、ほとんどまたはNULL値のみを含むこれらの列にあります。
カーン

回答:


8

非常に興味深いアプローチ。創造性に対する私の賛成票。

スペースを再利用したので、元のインデックスがなくなったと思いますか?フィルター処理されたインデックスの欠点は次のとおりです。

  • それらの数が多すぎると、オプティマイザの検索スペースが大きくなりすぎて、オプティマイザがタイムアウトするため、クエリプランが不十分になる可能性があります。
  • フィルターされていない同等のものは考慮されますが、フィルターされたインデックスが考慮されない状況がいくつかあります。特に、これは、インデックス付きの列でハッシュ結合を取得した場合、または列を(フィルターなしで)ORDER BYしようとした場合に発生する可能性があります。
  • クエリのパラメーター化はフィルターされたインデックスでは機能しません(http://www.sqlservercentral.com/blogs/practicalsqldba/2013/04/08/sql-server-part-9-filtered-index-a-new-way- for-performance-improvemnt /

実用的には、フィルターされたインデックスはひどいクエリプランになることが多いため、フィルターを使用する際は十分に注意する必要があります。私はそれらを役に立たないとは言いませんが、私はそれらを(あなたがしようとしているように)置き換えではなく、従来のインデックスへの追加として見ています。


「クエリのパラメーター化は、フィルター選択されたインデックスでは機能しません」。これはおそらくオプション(再コンパイル)で修正できます
MichaelD

2

Thomas Kejserがこのトピックに十分に回答しています。

私はちょうど2セントを加えることを考えました。

クエリのwhere句がフィルターされたインデックスのwhereと完全に一致する場合にのみ、いくつかのフィルターされたインデックスが使用される(実行プランに表示される)ことを確認しました。

インデックス付きビューを使用しようとしましたか? スパース列

内部ジョイントしかない限り、フィルター処理されたインデックスのwhere句を含むインデックス付きビューを作成でき、代わりにビューを使用できると思います。

複数のビューが存在する可能性があります。しかし、非クラスター化インデックスの場合と同じように、多すぎると書き込みが遅くなります。

私の経験では、読み取りでは良い結果が得られますが、テーブルがレプリケーションに関係している場合は、特に書き込み(挿入と更新)を監視する必要があります。

ただし、あなたの主な関心事は理解しているthe null valuesので、インデックス内の列SPARSEすることをお勧めします

スパース列は、フィルター処理されたインデックスに特に適しています

スパース列を宣伝しているので、その制限についても話さないと気分が悪くなります。

スパース列を含むテーブルを設計するときは、行が更新されるときに、テーブル内のnullでないスパース列ごとに2バイトのオーバーヘッドが追加で必要になることに注意してください。

結果的に

追加のメモリ要件。このメモリオーバーヘッドを含む合計行サイズが8019を超えると、更新が予期せずエラー576で失敗する可能性があります。

また、列を行から押し出すことはできません。

> bigint型のスパース列が600個あるテーブルの例を考えてみます。

null以外の列が571ある場合、ディスク上の合計サイズは571 * 12 = 6852バイトです。追加の行オーバーヘッドとスパース列ヘッダーを含めると、これは約6895バイトに増加します。ページには、ディスク上で利用可能な約1124バイトがまだあります。これにより、追加の列を正常に更新できるという印象を与えることができます。ただし、更新中は、メモリに2 *(null以外のスパース列の数)のオーバーヘッドが追加されます。この例では、追加のオーバーヘッド(2 * 571 = 1142バイト)を含めると、ディスクの行サイズが約8037バイトに増加します。このサイズは、最大許容サイズの8019バイトを超えています。すべての列は固定長データ型であるため、行からプッシュすることはできません。その結果、更新は576エラーで失敗します。

上記のリンクの詳細ですが、この警告もここに投稿したいと思います。

列をスパースから非スパースに、または非スパースからスパースに変更するには、列のストレージ形式を変更する必要があります。

SQL Serverデータベースエンジンは、次の手順を使用してこの変更を行います。

1-新しいストレージサイズとフォーマットで新しい列をテーブルに追加します。

2-テーブルの各行について、古い列に格納された値を更新して新しい列にコピーします。

3-テーブルスキーマから古い列を削除します。

4-テーブルを再構築する(クラスター化インデックスがない場合)、またはクラスター化インデックスを再構築して、古い列で使用されていた領域を再利用します。


1
こんにちは。少し遅れましたが、はい、かなり前にこのトピックで説明したアプローチを放棄しましたが、最近はより選択的なアプローチで戻ってきました。基本的に、統計の使用状況とビジネスモデルを調べて、テーブルごとにテーブルのインデックスを確認しました。次に、通常のインデックスの側に新しいフィルター処理されたインデックスを追加してテストし、数週間にわたってどれが最終的に使用されたかを確認しました。フィルター処理されたインデックスのみが新しいプランで使用されていることを確認した後、通常のフィルター処理されていないインデックスを削除しました。
カーン、

1
また、かなりの数の列をスパース型に変更しました。ただし、MSDNからわかるように、列の型をスパースに変更すると、基本的にクラスター化インデックス全体が再作成されます。大きくて複雑なテーブルでは、これをかなり重くします。そこで、制約とテーブルの名前を変更し、同じモデルと元の名前でスパース列を使用して新しい制約を作成し、データを適切なバッチで新しいテーブルに転送しました。次に、すべてが正常であること、およびすべてのインデックスとFKが再び配置されていることを確認した後、古いテーブルを削除しました。
カーン

1
また、場合によってはページ圧縮を使用する方がはるかに望ましいため、代わりにページ圧縮を使用することになりました。また、DROP_EXISTING = ONを指定して既存のクラスター化インデックスを作成するだけで、疎なルートを使用するよりもはるかに高速になるので便利です。特に、インデックスとFKを再管理する手間を全体的に回避するためです。
カーン
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.