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

フィルタリングされたインデックスがより強力な機能になる方法

    誤解しないでください。フィルター処理されたインデックスが大好きです。これらは、I / Oをはるかに効率的に使用する機会を生み出し、最終的に、適切なANSI準拠の一意の制約を実装できるようにします(複数のNULLが許可されている場合)。しかし、それらは完璧にはほど遠いです。フィルター処理されたインデックスを改善して、そこにあるワークロードの大部分に対してはるかに便利で実用的なものにすることができるいくつかの領域を指摘したいと思いました。

    まず、良いニュース

    フィルター処理されたインデックスは、以前は高価だったクエリを非常に迅速に処理できるため、使用するスペースが少なくて済みます(したがって、スキャンした場合でもI / Oが削減されます)。

    Sales.SalesOrderDetailEnlargedを使用した簡単な例 (Jonathan Kehayias(@SQLPoolBoy)によってこのスクリプトを使用して構築されました)。このテーブルには4.8MM行があり、587MBのデータと363MBのインデックスがあります。 null許容列はCarrierTrackingNumberだけです。 、それではそれで遊んでみましょう。現状では、テーブルには現在、これらの値の約半分(2.4MM)がNULLとして含まれています。フィルター処理されたインデックスの利点を最もよく強調するために、テーブル内の行のごく一部が実際にインデックスに適格であるシナリオをシミュレートするために、これを約240Kに減らします。次のクエリは2.17MM行に影響し、 CarrierTrackingNumberの値がNULLの241,507行を残します :

    UPDATE Sales.SalesOrderDetailEnlarged 
        SET CarrierTrackingNumber = 'x'
          WHERE CarrierTrackingNumber IS NULL
          AND SalesOrderID % 10 <> 3;

    ここで、追跡番号がまだ割り当てられていない製品を含む注文を常に確認したいというビジネス要件があるとします(分割されて別々に出荷される注文を考えてみてください)。現在のテーブルで、これらのクエリを実行します(そして、すべての場合にコールドキャッシュを確保するためにDBCCコマンドを追加しました):

    DBCC DROPCLEANBUFFERS;
    DBCC FREEPROCCACHE;
     
    SELECT COUNT(*)
      FROM Sales.SalesOrderDetailEnlarged 
      WHERE CarrierTrackingNumber IS NULL;
     
    SELECT ProductID, SalesOrderID
      FROM Sales.SalesOrderDetailEnlarged
      WHERE CarrierTrackingNumber IS NULL;

    これには、クラスター化インデックススキャンが必要であり、次のランタイムメトリックが生成されます(SQL Sentry Plan Explorerでキャプチャされたもの):

    「昔」(SQL Server 2005以降を意味する)では、このインデックスを作成していました(実際、SQL Server 2012でも、これはSQL Serverが推奨するインデックスです):

    CREATE INDEX IX_NotVeryHelpful
    ON [Sales].[SalesOrderDetailEnlarged] ([CarrierTrackingNumber])
    INCLUDE ([SalesOrderID],[ProductID]);

    そのインデックスを設定し、上記のクエリを再度実行すると、次のメトリックが表示されます。両方のクエリで、予想どおりにインデックスシークが使用されます。

    次に、そのインデックスを削除して少し異なるインデックスを作成し、 WHEREを追加するだけです。 条項:

    CREATE INDEX IX_Filtered_CTNisNULL
    ON [Sales].[SalesOrderDetailEnlarged] ([CarrierTrackingNumber])
    INCLUDE ([SalesOrderID],[ProductID])
    WHERE CarrierTrackingNumber IS NULL;

    これらの結果が得られ、両方のクエリでフィルタリングされたインデックスがシークに使用されます。

    上記のクエリの実行時間とI/Oの削減と比較して、各インデックスに必要な追加のスペースは次のとおりです。

    インデックス インデックススペース スペースを追加 期間 読み取り
    専用インデックスなし 363 MB 15,700ms 〜164,000
    フィルタリングされていないインデックス 530 MB 167 MB(+ 46%) 169ms 1,084
    フィルタリングされたインデックス 367 MB 4 MB(+ 1%) 170ms 1,084


    つまり、ご覧のとおり、フィルター処理されたインデックスは、フィルター処理されていないインデックスとほぼ同じパフォーマンスの向上を実現します(どちらも同じ数の読み取りを使用してデータを取得できるため)が、ストレージははるかに少なくなりますフィルタされたインデックスは、フィルタ述語に一致する行を格納および維持するだけでよいため、コストがかかります。

    それでは、テーブルを元の状態に戻しましょう。

    UPDATE Sales.SalesOrderDetailEnlarged
      SET CarrierTrackingNumber = NULL
      WHERE CarrierTrackingNumber = 'x';
     
    DROP INDEX IX_NotVeryHelpful ON Sales.SalesOrderDetailEnlarged;
    DROP INDEX IX_Filtered_CTNisNULL ON Sales.SalesOrderDetailEnlarged;

    Tim Chapman(@chapmandew)とMichelle Ufford(@sqlfool)は、独自の方法でフィルター処理されたインデックスのパフォーマンス上の利点を概説する素晴らしい仕事をしました。彼らの投稿もチェックする必要があります:

    • Michelle Ufford:フィルター処理されたインデックス:知っておくべきこと
    • Tim Chapman:フィルター処理されたインデックスの喜び

    また、ANSI準拠の固有の制約(一種)

    また、ANSI準拠の固有の制約についても簡単に説明したいと思いました。 SQL Server 2005では、次のような一意の制約を作成します。

    CREATE TABLE dbo.Personnel
    (
      EmployeeID INT PRIMARY KEY,
      SSN CHAR(9) NULL,
      -- ... other columns ...
      CONSTRAINT UQ_SSN UNIQUE(SSN)
    );

    (制約の代わりに一意の非クラスター化インデックスを作成することもできます。基本的な実装は基本的に同じです。)

    さて、エントリー時にSSNがわかっていれば、これは問題ありません:

    INSERT dbo.Personnel(EmployeeID, SSN)
    VALUES(1,'111111111'),(2,'111111112');

    入国時に知られていないSSNが時折ある場合も問題ありません(ビザ申請者、またはSSNを持っておらず、決してそうしない外国人労働者を考えてみてください):

    INSERT dbo.Personnel(EmployeeID, SSN)
    VALUES(3,NULL);

    ここまでは順調ですね。しかし、があるとどうなりますか 社会保障番号が不明な従業員ですか?

    INSERT dbo.Personnel(EmployeeID, SSN)
    VALUES(4,NULL);

    結果:

    メッセージ2627、レベル14、状態1、行1
    UNIQUEKEY制約「UQ_SSN」の違反。オブジェクト'dbo.Personnel'に重複するキーを挿入できません。重複するキー値は()です。
    ステートメントは終了しました。

    したがって、この列に存在できるNULL値は常に1つだけです。ほとんどのシナリオとは異なり、これはSQL Serverが2つのNULL値を等しいものとして扱う1つのケースです(等しいことが単に不明であり、次にfalseであると判断するのではありません)。人々は何年もの間、この矛盾について不平を言ってきました。

    これが要件である場合、フィルタリングされたインデックスを使用してこれを回避できるようになりました:

    ALTER TABLE dbo.Personnel DROP CONSTRAINT UQ_SSN;
    GO
     
    CREATE UNIQUE INDEX UQ_SSN ON dbo.Personnel(SSN)
      WHERE SSN IS NOT NULL;

    一意性はNULL以外の値にのみ適用されるため、4番目の挿入は問題なく機能します。これは一種の不正行為ですが、ANSI規格が意図した基本的な要件を満たしています(SQLServerではALTER TABLE ... ADD CONSTRAINT の使用は許可されていませんが) フィルタされた一意性制約を作成するための構文)。

    しかし、電話を持ってください

    これらは、フィルタリングされたインデックスでできることの優れた例ですが、まだできないことがたくさんあり、その結果、いくつかの制限や問題が発生します。

    統計の更新

    これは、私見で最も重要な制限の1つです。フィルターされた索引は、フィルター述部によって識別されるテーブルのサブセットの変化率に基づく統計の自動更新の恩恵を受けません。これは、(フィルター処理されていないすべてのインデックスと同様に)テーブル全体に対するチャーンに基づいています。これは、フィルタリングされたインデックスに含まれるテーブルの割合に応じて、インデックスの行数が4倍または半分になる可能性があり、手動で行わない限り統計が更新されないことを意味します。 Kimberly Trippは、これに関するいくつかの優れた情報を提供しています(Gail Shawは、10,000行のみを含むフィルター処理されたインデックスの統計が更新されるまでに257,000回の更新が必要だった例を引用しています):

    http://www.sqlskills.com/blogs/kimberly/filtered-indexes-and-filtered-stats-might-become-seriously-out-of-date/
    http://www.sqlskills.com/ blogs / kimberly / category / filtered-indexes /

    また、キンバリーの同僚であるJoe Sack(@JosephSack)は、フィルター処理されたインデックスとフィルター処理された統計の両方でこの動作を修正することを提案するConnectアイテムを提出しました。

    フィルター式の制限

    NOT IN など、フィルター述語で使用できない構造がいくつかあります。 、または WHERE col> =DATEADD(DAY、-1、GETDATE())のような動的/非決定論的述語 。また、述語が WHERE と完全に一致しない場合、オプティマイザはフィルタリングされたインデックスを認識しない可能性があります。 インデックス定義の句。ここに、より良いカバレッジのためにいくつかのサポートを誘導しようとするいくつかのConnectアイテムがあります:

    フィルター処理されたインデックスでは、論理和のフィルターは許可されません (クローズ:設計による)
    フィルターされたインデックスの作成がNOTIN句で失敗しました (クローズ:設計による)
    フィルター処理されたインデックスでのより複雑なWHERE句のサポート (アクティブ)

    他の潜在的な用途は現在不可能です

    現在、確定的であっても、永続化された計算列にフィルター処理されたインデックスを作成することはできません。一意のフィルター処理されたインデックスに外部キーを指定することはできません。フィルター処理されたインデックスでサポートされるクエリに加えて、インデックスで外部キーをサポートする場合は、2番目の冗長なフィルター処理されていないインデックスを作成する必要があります。そして、見落とされているか、まだ考慮されていない他のいくつかの同様の制限があります:

    決定論的で永続化された計算列にフィルター処理されたインデックスを作成できる必要があります (アクティブ)
    フィルタリングされた一意のインデックスを外部キーの候補キーにする (アクティブ)
    インデックス付きビューにフィルターインデックスを作成する機能 (クローズ:修正されません)
    パーティショニングエラー1908–パーティショニングの強化 (クローズ:修正されません)
    「フィルター処理された」列ストアインデックスを作成する (アクティブ)

    MERGEの問題

    そしてMERGE 私の「気を付けろ」リストにさらに別の登場をします:

    MERGEは、操作後ではなく、行ごとにフィルター処理されたインデックスを評価します。これにより、フィルター処理されたインデックス違反が発生します (クローズ:修正されません)
    MERGEは、フィルタリングされたインデックスが適切に設定された状態で更新に失敗します (クローズ:固定)
    INSERT/DELETEがインデックスを使用およびフィルタリングしたときのMERGEステートメントのバグ (アクティブ)
    MERGEが一意の主要な違反を誤って報告する (アクティブ)


    これらの(密接に関連しているように見える)バグの1つは、SQL Server 2012で修正されていると述べていますが、特に以前のバージョンでこの問題のバリエーションが発生した場合(またはMERGEの使用を停止した場合)は、PSSに連絡する必要があります。 、前に提案したように)。

    ツール/DMV/組み込みの制限

    多くのDMV、DBCCコマンド、システムプロシージャ、およびクライアントツールがあり、時間の経過とともに信頼できるようになります。ただし、これらすべてが新機能を利用するように更新されているわけではありません。フィルタリングされたインデックスも例外ではありません。次のConnectアイテムは、フィルター処理されたインデックスで機能することを期待している場合に、つまずく可能性のあるいくつかの問題を示しています。

    新しいテーブルの設計中にSSMSからフィルター処理されたインデックスを作成する方法はありません (クローズ:修正されません)
    テーブルデザイナによってテーブルが変更されると、フィルタリングされたインデックスのフィルタ式が失われます (クローズ:修正されません)
    テーブルデザイナはフィルタリングされたインデックスにWHERE句を記述しません (アクティブ)
    SSMSテーブルデザイナは、テーブルの再構築時にインデックスフィルタ式を保持しません (クローズ:修正されません)
    フィルターされたインデックスを使用したDBCCPAGEの誤った出力 (アクティブ)
    DMビューおよびDTAからのSQL2008フィルター処理されたインデックスの提案 (クローズ:修正されません)
    フィルタリングされたインデックスの欠落インデックスDMVの拡張 (クローズ:修正されません)
    圧縮されたフィルター処理されたインデックスを複製するときの構文エラー (クローズ:修正されません)
    エージェント:T-SQLスクリプトを実行するときに、ジョブはデフォルト以外のオプションを使用します (クローズ:修正されません)
    ビューの依存関係がTransact-SQLエラー515で失敗する (アクティブ)
    特定のオブジェクトで依存関係の表示に失敗する (クローズ:修正されません)
    2つのデータベースのスキーマ比較でインデックスオプションの違いが検出されない (クローズ:外部)
    インデックス情報のすべてのビューでインデックスフィルター条件を公開することを提案します (クローズ:修正されません)
    sp_helpIndexの結果には、フィルターインデックスのフィルター式を含める必要があります (アクティブ)
    2008機能のsp_help、sp_columns、sp_helpindexをオーバーロードします (クローズ:修正されません)


    最後の3つについては、息を止めないでください– Microsoftがsp_プロシージャ、DMV、INFORMATION_SCHEMAビューなどにいつでも投資する可能性はほとんどありません。代わりに、KimberlyTrippのsp_helpindexの書き換えを参照してください。 Microsoftが残した他の新機能を備えています。

    オプティマイザーの制限

    フィルター処理されたインデックスがオプティマイザーによって使用される可能性があるが、代わりに無視される場合を説明するConnect項目がいくつかあります。場合によっては、これらは「バグ」ではなく「機能のギャップ」と見なされます…

    SQLは、単純なクエリでフィルター処理されたインデックスを使用しません (クローズ:設計による)
    フィルター処理されたインデックスの実行プランが最適化されていません (クローズ:修正されません)
    フィルター処理されたインデックスは使用されず、出力のないキールックアップ (クローズ:修正されません)
    BIT列でのフィルター処理されたインデックスの使用法は、WHERE句で使用される正確なSQL式によって異なります (アクティブ)
    フィルタリングされた一意のインデックスが存在する場合、リンクサーバークエリは適切に最適化されません (クローズ:修正されません)
    Row_Number()は、フィルター処理されたインデックスが使用されているリンクサーバーで予測できない結果をもたらします (クローズ:再現なし)
    QPで使用されていない明らかなフィルター処理されたインデックス (クローズ:設計による)
    一意のフィルター処理されたインデックスを一意として認識します (アクティブ)


    Paul White(@SQL_Kiwi)は最近SQLPerformance.comに投稿し、上記のオプティマイザーの制限のいくつかについて詳細に説明しています。

    また、Tim Chapmanは、述語をローカル変数に一致させることができない(2008 R2 SP1で修正)、インデックスヒントでフィルター処理されたインデックスを指定できないなど、フィルター処理されたインデックスの他の制限について概説したすばらしい投稿を書きました。

    >

    結論

    フィルター処理されたインデックスには大きな可能性があり、SQL Server 2008で最初に導入されたとき、私はそれらに非常に大きな期待を寄せていました。ただし、最初のバージョンに付属していた制限のほとんどは、今日でも1年半(場合によっては2つ)存在します。展望)後のメジャーリリース。上記は、対処する必要のあるアイテムのかなり広範な洗濯物リストのように見えますが、私はそれがそのように出くわすつもりはありませんでした。フィルタリングされたインデックスを利用する際に考慮する必要のある潜在的な問題の数が非常に多いことを人々に知ってもらいたいだけです。


    1. Mysqlレプリケーションの場合にマスターとスレーブのデータベースが異なる場合にMysqlDBを再同期するにはどうすればよいですか?

    2. proxysql-admin代替-ClusterControlProxySQLGUI

    3. SQL:NOTINとNOTEQUAL TOに関しては、どちらがより効率的で、なぜですか?

    4. SQL Serverの変数列名?