次の方法で共有


インデックスのアーキテクチャと設計ガイド

適用対象:SQL ServerAzure SQL DatabaseAzure SQL Managed InstanceMicrosoft Fabric の SQL データベース

効率的なインデックスの設計は、優れたデータベースとアプリケーションのパフォーマンスを実現するための鍵となります。 インデックスの不足、インデックスの過剰なインデックス作成、またはインデックスの設計が不十分な場合は、データベース パフォーマンスの問題の主な原因となります。

このガイドでは、インデックスのアーキテクチャと基礎について説明し、アプリケーションのニーズに合わせて効果的なインデックスを設計するのに役立つベスト プラクティスを提供します。

使用できるインデックスの種類の詳細については、「 インデックス」を参照してください。

このガイドでは、次のインデックスの種類について説明します。

プライマリ ストレージの形式 インデックスの種類
ディスク ベースの行ストア
Clustered
Nonclustered
Unique
Filtered
Columnstore
クラスター化列ストア
非クラスター化列ストア
Memory-optimized
Hash
メモリ最適化型ノンクラスタード

XML インデックスについては、「XML インデックス (SQL Server)」および「選択的 XML インデックス (SXI)」をご覧ください。

空間インデックスについては、「空間インデックスの概要」をご覧ください。

フルテキスト インデックスの詳細については、「フルテキスト インデックスの作成」を参照してください。

インデックスの基本

通常の書籍について考えてみましょう。書籍の最後には、その書籍内の情報をすばやく検索するのに役立つインデックスがあります。 インデックスは、並べ替えられたキーワードのリストであり、各キーワードの横には、各キーワードが記載されているページを指す一連のページ番号があります。

行ストア インデックスも同様です。これは値の順序付きリストであり、値ごとにこれらの値が配置されているデータ ページ へのポインターがあります。 インデックス自体は、インデックス ページと呼ばれるページにも格納されます。 通常の書籍では、インデックスが複数のページにまたがる場合、たとえば、 SQL という単語を含むすべてのページへのポインターを検索する必要がある場合は、キーワード SQLを含むインデックス ページを見つけるまで、インデックスの先頭からリーフスルーする必要があります。 そこから、書籍のすべてのページへのポインターに従います。 インデックスの先頭で、各文字が見つかるアルファベット順のリストが記載されたページを 1 ページを作成すれば、さらに最適化されます。 "A から D - 121 ページ"、"E から G - 122 ページ" など。 この追加ページがあれば、インデックスのページをめくって始まりの場所を見つける手順を省くことができます。 このようなページは、通常の書籍にはありませんが、行ストア インデックスにはあります。 この 1 ページは、インデックスのルート ページと呼ばれます。 ルート ページとは、インデックスによって使用されるツリー構造の開始ページです。 ツリーの比喩で言えば、実際のデータへのポインターを含む最終ページは、ツリーの "リーフ ページ" と呼ばれます。

インデックスとは、テーブルまたはビューに関連付けられたディスク上またはメモリ内の構造で、テーブルやビューからの行の取得を高速化します。 行ストア インデックスには、テーブルまたはビューの 1 つ以上の列の値から構築されたキーが含まれます。 行ストア インデックスの場合、これらのキーはツリー構造 (B+ ツリー) に格納されます。これにより、データベース エンジンはキー値に関連付けられている行を迅速かつ効率的に検索できます。

行ストア インデックスは、行と列を含むテーブルとして論理的に編成され、行 ストア1 と呼ばれる行ごとのデータ形式で物理的に格納されるデータを格納します。 データを列ごとに格納する別の方法として、カラムストアがあります。

データベースとそのワークロードに適したインデックスの設計は、クエリ速度、インデックス更新コスト、ストレージ コストの間で複雑なバランスを取ります。 狭いディスク ベースの行ストア インデックス、またはインデックス キーに列が少ないインデックスでは、必要なストレージ領域が少なくなり、更新のオーバーヘッドが小さくなります。 一方、ワイド インデックスは、より多くのクエリを向上させる可能性があります。 インデックスの最も効率的なセットを見つける前に、いくつかの異なる設計を試す必要がある場合があります。 アプリケーションが進化するにつれて、最適なパフォーマンスを維持するためにインデックスの変更が必要になる場合があります。 インデックスは、データベース スキーマやアプリケーションの設計に影響を与えずに追加、変更、削除できます。 さまざまなデザインのインデックスを積極的にテストするようにしてください。

通常、データベース エンジンのクエリ オプティマイザーは、クエリを実行する最も効果的なインデックスを選択します。 クエリ オプティマイザーが特定のクエリに使用するインデックスを確認するには、SQL Server Management Studio の [クエリ ] メニューの [ 推定実行プランの表示 ] または [実際の実行プランを含める] を選択します。

インデックスを使用しても、常にパフォーマンスが向上するわけではありません。また、パフォーマンスが優れていても、常にインデックスが効率的に使用されているわけでもありません。 インデックスを使用すれば常にパフォーマンスが向上するならば、クエリ オプティマイザーのジョブは単純です。 しかし実際には、不適切なインデックスを選択すると、最適なパフォーマンスを実現することはできません。 そのため、クエリ オプティマイザーのタスクは、パフォーマンスが向上した場合にのみインデックスまたはインデックスの組み合わせを選択し、パフォーマンスが低下したときにインデックスの取得を回避することです。

一般的な設計上の間違いは、多くのインデックスを投機的に作成し、"オプティマイザーに選択肢を与える" ためです。 結果としてオーバーインデックスが行われ、データの変更が遅くなり、コンカレンシーの問題が発生する可能性があります。

1 列ストアは、リレーショナル テーブル データを格納する従来の方法です。 行ストア とは、基になるデータ ストレージ形式がヒープ、B+ ツリー (クラスター化インデックス)、またはメモリ最適化テーブルであるテーブルを指します。 ディスクベースの行ストアでは、メモリ最適化テーブルは除外されます。

インデックスのデザインの作業

インデックスをデザインするには、次の作業を行うことをお勧めします。

  1. データベースとアプリケーションの特性を理解します

    たとえば、高いスループットを維持する必要がある頻繁なデータ変更を伴うオンライン トランザクション処理 (OLTP) データベースでは、最も重要なクエリを対象とするいくつかの狭い行ストア インデックスが適切な初期インデックス設計になります。 非常に高いスループットを実現するには、ロックとラッチフリーの設計を提供するメモリ最適化テーブルとインデックスを検討してください。 詳細については、このガイド の「メモリ最適化非クラスター化インデックス設計ガイドライン 」および 「ハッシュ インデックス設計ガイドライン 」を参照してください。

    逆に、非常に大きなデータ セットをすばやく処理する必要がある分析またはデータ ウェアハウス (OLAP) データベースの場合は、クラスター化列ストア インデックスを使用することが特に適しています。 詳細については、このガイドの 「列ストア インデックス: 概要 」または 「列ストア インデックスのアーキテクチャ 」を参照してください。

  2. 最も頻繁に使用されるクエリの特性を理解します

    たとえば、頻繁に使用されるクエリが 2 つ以上のテーブルを結合することを認識すると、これらのテーブルのインデックスのセットを決定するのに役立ちます。

  3. クエリ述語で使用される列のデータ分布について説明します

    たとえば、インデックスは多数の異なるデータ値を持つ列に役立ちますが、重複する値が多い列に対してはそれほど役に立ちません。 NULL が多い列、またはデータのサブセットが明確に定義されている列の場合は、フィルター選択されたインデックスを使用できます。 詳細については、このガイドの「フィルター選択されたインデックスのデザイン ガイドライン」を参照してください。

  4. パフォーマンスを向上させるインデックス オプションを決定します。

    たとえば、既存の大きなテーブルにクラスター化インデックスを作成すると、 ONLINE インデックス オプションのメリットが得られる場合があります。 ONLINE オプションを使用すると、インデックスの作成中または再構築中に、基になるデータで同時処理を続行できます。 行またはページ のデータ圧縮 を使用すると、インデックスの I/O とメモリ占有領域を減らすことでパフォーマンスを向上させることができます。 詳細については、「 CREATE INDEX」を参照してください。

  5. テーブル上の既存のインデックスを調べて、重複するインデックスや非常によく似たインデックスが作成されないようにします

    多くの場合、新しいインデックスを作成するよりも、既存のインデックスを変更する方がよいです。ただし、それがほとんど重複している場合は別です。 たとえば、これらの列を使用して新しいインデックスを作成するのではなく、1 つまたは 2 つの追加の含まれる列を既存のインデックスに追加することを検討してください。 これは、インデックス候補が 見つからない非クラスター化インデックスをチューニングする場合、または データベース エンジン チューニング アドバイザーを使用する場合に特に関連します。データベース エンジン チューニング アドバイザーでは、同じテーブルと列で同様の種類のインデックスが提供される可能性があります。

インデックスのデザインの全般的なガイドライン

データベース、クエリ、およびテーブル列の特性を理解することは、最適なインデックスを最初に設計し、アプリケーションの進化に合わせて設計を変更するのに役立ちます。

データベースに関する考慮事項

インデックスをデザインするときは、次のデータベースのガイドラインを考慮してください。

  • テーブルのインデックスの数が多い場合は、テーブル内のデータの変更に応じてインデックス内のデータを変更する必要があるため、 INSERTUPDATEDELETE、および MERGE ステートメントのパフォーマンスに影響します。 たとえば、1 つの列が複数のインデックスで使用されていて、その列のデータを変更する UPDATE ステートメントを実行する場合、その列を含む各インデックスも更新する必要があります。

    • 頻繁に更新するテーブルにはインデックスをデザインしすぎないようにし、インデックスの幅を狭く、つまり列数を可能な限り少なくします。

    • データの変更が少ないが大量のデータを含むテーブルでは、インデックスを増やすことができます。 このようなテーブルでは、さまざまなインデックスがクエリのパフォーマンスに役立ちますが、インデックスの更新のオーバーヘッドは引き続き許容されます。 ただし、投機的にインデックスを作成しないでください。 インデックスの使用状況を監視し、時間の経過と同時に未使用のインデックスを削除します。

  • データベース エンジンは、ベース テーブル スキャンを実行するよりも、データを検索するインデックスを走査するのに時間がかかるため、小さなテーブルのインデックス作成が最適でない場合があります。 そのため、小さいテーブルのインデックスは使用されない可能性がありますが、テーブル内のデータが更新されるときには、引き続き更新する必要があります。

  • ビューのインデックスは、ビューに集計や結合が含まれている場合に、パフォーマンスを大幅に向上させることができます。 詳細については、「 インデックス付きビューの作成」を参照してください。

  • Azure SQL Database のプライマリ レプリカのデータベースでは、インデックスに対するデータベースアドバイザーのパフォーマンスに関する推奨事項が自動的に生成されます。 必要に応じて、インデックスの自動チューニングを有効にすることができます。

  • クエリ ストアは、パフォーマンスが最適でないクエリを識別するのに役立ち 、オプティマイザーによって選択されたインデックスを表示できる クエリ実行プラン の履歴を提供します。 このデータを使用すると、最も頻繁にリソースを消費するクエリに焦点を当てることで、インデックスチューニングの変更に最も影響を与えることができます。

クエリに関する考慮事項

インデックスをデザインするときは、次のクエリのガイドラインを考慮してください。

  • 述語で頻繁に使用される列に非クラスター化インデックスを作成し、クエリで式を結合します。 これらは SARGable 列です 。 ただし、インデックスに不要な列を追加しないようにする必要があります。 インデックス列を追加しすぎると、ディスク領域とインデックス更新のパフォーマンスに悪影響を及ぼす可能性があります。

    リレーショナルデータベースにおける SARGable という用語は、Search ARGumentable な述語を指し、これはインデックスを使用することでクエリの実行を高速化できることを意味します。 詳細については、 SQL Server と Azure SQL インデックスのアーキテクチャと設計ガイドを参照してください。

    Tip

    作成するインデックスがクエリ ワークロードによって実際に使用されていることを常に確認してください。 未使用のインデックスを削除します。

    インデックスの使用状況の統計情報は 、sys.dm_db_index_usage_statssys.dm_db_index_operational_statsで使用できます。

  • クエリの対象にインデックスを含めると、クエリのパフォーマンスを向上できます。これは、クエリの要件を満たすために必要なデータがすべて、インデックス自体に保持されているためです。 つまり、要求されたデータの取得に必要なのはインデックス ページだけで、テーブルやクラスター化インデックスのデータ ページは必要ありません。このため、全体的にディスク I/O を削減できます。 たとえば、テーブルの列 AB に対するクエリは、このテーブルに列 ABC に基づく複合インデックスが作成されていれば、インデックスのみから指定したデータを取得できます。

    Note

    カバー インデックスは、ベース テーブルにアクセスせずにクエリによるすべてのデータ アクセスを直接満たす非クラスター化インデックスです。

    このようなインデックスには、インデックス キーに必要なすべての SARGable 列と、含まれる列として SARGable 以外の列があります。 つまり、 WHEREJOINGROUP BY 句、または SELECT 句または UPDATE 句のいずれかで、クエリに必要なすべての列がインデックスに存在します。

    テーブル自体の行と列と比較してインデックスが十分に狭い場合は、クエリを実行する I/O がはるかに少なくなる可能性があります。つまり、すべての列の小さなサブセットです。

    大きなテーブルの小さな部分を取得し、その小さな部分が固定述語によって定義される場合は、インデックスをカバーすることを検討してください。

    データベースストレージ、I/O、メモリフットプリントを増やしている間にメリットが減少するため、列が多すぎるカバーインデックスを作成しないでください。

  • 複数のクエリを使用して同じ行を更新するよりも、1 つのステートメントでできるだけ多くの行を挿入または変更するクエリを作成します。 これにより、インデックス更新のオーバーヘッドが軽減されます。

列に関する考慮事項

インデックスをデザインするときは、次の列のガイドラインを考慮してください。

  • 特にクラスター化インデックスの場合は、インデックス キーの長さを短くします。

  • ntexttextimagevarchar(max)nvarchar(max)varbinary(max)json、および vector データ型の列は、インデックス キー列として指定できません。 ただし、これらのデータ型を持つ列は、非クラスター化インデックスに非キー (含まれる) インデックス列として追加できます。 詳細については、このガイドの 「非クラスター化インデックスに含まれる列を使用する 」セクションを参照してください。

  • 列の一意性を調べます。 同じキー列の一意でないインデックスの代わりに一意のインデックスを使用すると、クエリ オプティマイザーに関する追加情報が提供され、インデックスがより役立ちます。 詳細については、このガイドの「一意なインデックスのデザイン ガイドライン」を参照してください。

  • 列内のデータの分布を調べます。 多数の行を含む列にインデックスを作成しても、異なる値が少ない場合は、インデックスがクエリ オプティマイザーによって使用されている場合でも、クエリのパフォーマンスが向上しない可能性があります。 たとえば、家族名でアルファベット順に並べ替えられた物理的な電話のディレクトリでは、市内のすべてのユーザーが Smith または Jones という名前の場合、人を特定する時間が短縮されません。 データ分布の詳細については、「 統計」を参照してください。

  • 適切に定義されたサブセットを持つ列 (多くの NULL を含む列、値のカテゴリを持つ列、個別の値の範囲を持つ列など) でフィルター選択されたインデックスを使用することを検討してください。 適切に設計されたフィルター選択されたインデックスを使用すると、そのサブセットが多くのクエリに関連する場合に、テーブル内のすべての行の小さなサブセットを格納することで、クエリのパフォーマンスを向上させ、インデックスの更新コストを削減し、ストレージ コストを削減できます。

  • キーに複数の列が含まれている場合は、インデックス キー列の順序を考慮してください。 等値 (=)、不等値 (>>=<<=)、または BETWEEN 式でクエリ述語で使用される列、または結合に参加している列は、最初に配置する必要があります。 その他の列は、差異の程度、つまり最も差異の大きいものから最も差異の小さいものの順に配置します。

    たとえば、インデックスがLastNameFirstNameとして定義されている場合、インデックスは、WHERE句のクエリ述語がWHERE LastName = 'Smith'またはWHERE LastName = Smith AND FirstName LIKE 'J%'場合に便利です。 ただし、クエリ オプティマイザーでは、 WHERE FirstName = 'Jane'でのみ検索されたクエリにインデックスを使用しないか、インデックスによってこのようなクエリのパフォーマンスが向上しません。

  • 計算列がクエリ述語に含まれている場合は、インデックス作成を検討してください。 詳細については、「計算列のインデックス」を参照してください。

インデックスの特性

クエリにインデックスを設定することが適切であると判断した場合は、状況に応じて最適な種類のインデックスを選択します。 インデックスの特性は次のとおりです。

  • クラスター化または非クラスター化
  • 一意または一意でない
  • 単一列または複数列
  • インデックス内のキー列の昇順または降順
  • 非クラスター化インデックスでは、すべての行またはフィルターされた行を使用します。
  • 列ストアまたは行ストア
  • メモリ最適化テーブルのハッシュまたは非クラスター化

ファイル グループまたはパーティション構成に対するインデックス配置

インデックスの設計について考えるときは、データベースに関連付けられたファイル グループ上にインデックスを配置することを検討する必要があります。

既定では、インデックスは、インデックスが作成されるベース テーブル (クラスター化インデックスまたはヒープ) と同じファイル グループに格納されます。 次のようなその他の構成が可能です。

  • ベース テーブルのファイル グループ以外のファイル グループに非クラスター化インデックスを作成します。

  • 複数のファイル グループにまたがるクラスター化インデックスおよび非クラスター化インデックスをパーティション分割する。

パーティション分割されていないテーブルの場合、通常、最も簡単な方法が最適です。すべてのテーブルを同じファイル グループに作成し、必要な数のデータ ファイルをファイル グループに追加して、使用可能なすべての物理ストレージを利用します。

階層化ストレージが使用可能な場合は、より高度なインデックス配置方法を検討できます。 たとえば、高速ディスク上のファイルを含む頻繁にアクセスされるテーブルのファイル グループと、低速のディスク上のアーカイブ テーブル用のファイル グループを作成できます。

クラスター化インデックスのあるテーブルをファイル グループ間で移動するには、クラスター化インデックスを削除し、MOVE TO ステートメントのDROP INDEX句で新しいファイル グループまたはパーティション構成を指定するか、CREATE INDEX句で DROP_EXISTING ステートメントを使用します。

パーティション インデックス

複数のファイル グループ間でディスク ベースのヒープ、クラスター化インデックス、非クラスター化インデックスをパーティション分割することも検討できます。 パーティションインデックスは、パーティション関数に基づいて水平方向 (行単位) にパーティション分割されます。 パーティション関数は、パーティション分割列と呼ばれる、指定した特定の列の値に基づいて、各行をパーティションにマップする方法を定義します。 パーティション構成では、一連のパーティションのファイル グループへのマッピングを指定します。

インデックスをパーティション分割すると、次のような利点があります。

  • 大規模なデータベースの管理を容易にします。 たとえば、OLAP システムでは、データの一括追加と削除を大幅に簡略化するパーティション対応 ETL を実装できます。

  • 実行時間の長い分析クエリなど、特定の種類のクエリを高速に実行します。 クエリでパーティション インデックスが使用されている場合、データベース エンジンは複数のパーティションを同時に処理し、クエリで必要のないパーティションをスキップ (削除) できます。

Warnung

パーティション分割によって OLTP システムのクエリ パフォーマンスが向上することはめったにありませんが、トランザクション クエリが多数のパーティションにアクセスする必要がある場合、大幅なオーバーヘッドが発生する可能性があります。

詳細については、「パーティション テーブルとインデックス」を参照してください。

インデックス並べ替え順のデザイン ガイドライン

インデックスを定義する場合は、各インデックス キー列を昇順または降順に格納する必要があるかどうかを検討してください。 [昇順] が既定値です。 CREATE INDEXCREATE TABLE、および ALTER TABLE の各ステートメントの構文では、インデックスと制約の個別の列にキーワード ASC (昇順) と DESC (降順) を使用できます。

インデックスにキー値が格納される順序を指定することは、テーブルを参照しているクエリに ORDER BY 句があり、そのインデックスの 1 つ以上のキー列が ORDER BY 句によって異なる方向に指定されている場合に役立ちます。 このような場合、インデックスを使用すると、クエリ プランで Sort演算子 が不要になることがあります。

たとえば、Adventure Works Cyclesの購買部のバイヤーが、業者から購入する製品の品質を評価する必要がある場合について考えてみます。 購入者は、拒否率の高いベンダーから送信された製品を見つけることに最も関心があります。

AdventureWorks サンプル データベースに対する次のクエリに示すように、この基準を満たすデータを取得するには、RejectedQty テーブルの Purchasing.PurchaseOrderDetail 列を降順 (大から小) に並べ替え、ProductID 列を昇順 (小から大) に並べ替える必要があります。

SELECT RejectedQty,
       ((RejectedQty / OrderQty) * 100) AS RejectionRate,
       ProductID,
       DueDate
FROM Purchasing.PurchaseOrderDetail
ORDER BY RejectedQty DESC, ProductID ASC;

このクエリの次の 実行プラン は、クエリ オプティマイザーが Sort 演算子を使用して、 ORDER BY 句で指定された順序で結果セットを返したことを示しています。

このクエリの実行プランで、クエリ オプティマイザーにより SORT 操作が使用され、ORDER BY 句で指定された順序で結果セットが返されたことを示す図。

クエリの ORDER BY 句のキー列と一致するキー列を使用してディスク ベースの行ストア インデックスを作成すると、クエリ プランの Sort 演算子が削除され、クエリ プランの効率が向上します。

CREATE NONCLUSTERED INDEX IX_PurchaseOrderDetail_RejectedQty
ON Purchasing.PurchaseOrderDetail
    (RejectedQty DESC, ProductID ASC, DueDate, OrderQty);

クエリが再度実行された後、次の実行プランでは 、Sort 演算子が存在しなくなり、新しく作成された非クラスター化インデックスが使用されていることが示されます。

実行プランで SORT 操作が削除され、新しく作成された非クラスター化インデックスが使用されたことを示す図。

データベース エンジンは、どちらの方向でもインデックスをスキャンできます。 RejectedQty DESC, ProductID ASCとして定義されたインデックスは、ORDER BY句の列の並べ替え方向が逆になるクエリに引き続き使用できます。 たとえば、 ORDER BY 句を持つクエリ ORDER BY RejectedQty ASC, ProductID DESC 同じインデックスを使用できます。

並べ替え順序は、インデックスでキー列のみに指定できます。 sys.index_columns カタログ ビューは、インデックス列が昇順または降順で格納されているかどうかを報告します。

クラスター化インデックスの設計ガイドライン

クラスター化インデックスには、テーブルのすべての行とすべての列が格納されます。 行は、インデックス キー値の順序で並べ替えられます。 テーブルごとにクラスター化インデックスは 1 つだけ存在できます。

ベース テーブルという用語は、クラスター化インデックスまたはヒープを参照できます。 ヒープは、テーブルのすべての行とすべての列を含むディスク上の 並べ替えされていない データ構造です。

いくつかの例外を除き、すべてのテーブルにクラスター化インデックスが必要です。 クラスター化インデックスの望ましいプロパティは次のとおりです。

プロパティ Description
狭い クラスター化インデックス キーは、同じベース テーブル上の非クラスター化インデックスの一部です。 狭いキー、またはキー列の合計長が小さいキーは、テーブル上のすべてのインデックスのストレージ、I/O、およびメモリのオーバーヘッドを軽減します。

キーの長さを計算するには、キー列で使用されるデータ型のストレージ サイズを加算します。 詳細については、「 データ型のカテゴリ」を参照してください。
ユニーク クラスター化インデックスが一意でない場合は、一意性を確保するために、4 バイトの内部 uniqueifier 列がインデックス キーに自動的に追加されます。 クラスター化インデックス キーに既存の一意の列を追加すると、テーブル上のすべてのインデックス内の uniqueifier 列のストレージ、I/O、およびメモリのオーバーヘッドが回避されます。 さらに、インデックスが一意の場合、クエリ オプティマイザーは、より効率的なクエリ プランを生成できます。
増え続ける 増え続けるインデックスでは、データは常にインデックスの最後のページに追加されます。 これにより、インデックスの途中でのページ分割が回避され、 ページ密度 が低下し、パフォーマンスが低下します。
不変 クラスター化インデックス キーは、非クラスター化インデックスの一部です。 クラスター化インデックスのキー列を変更する場合は、すべての非クラスター化インデックスでも変更を行う必要があります。これによって、CPU、ログ記録、I/O、メモリのオーバーヘッドが追加されます。 クラスター化インデックスのキー列が変更できない場合、オーバーヘッドは回避されます。
null値を許容する列のみを持っている 行に null 許容列がある場合は、 NULL ブロックと呼ばれる内部構造を含める必要があります。NULL ブロックは、インデックス内の行ごとに 3 ~ 4 バイトのストレージを追加します。 クラスター化インデックスのすべての列を null 許容にしないようにすると、このオーバーヘッドが回避されます。
固定幅の列のみを持つ varcharnvarchar などの可変幅データ型を使用する列では、固定幅データ型と比較して、値あたり 2 バイトが追加されます。 int などの固定幅データ型を使用すると、テーブル上のすべてのインデックスでこのオーバーヘッドが回避されます。

クラスター化インデックスを設計するときに、これらのプロパティをできるだけ多く満たすことで、クラスター化インデックスだけでなく、同じテーブル上のすべての非クラスター化インデックスの効率が向上します。 ストレージ、I/O、メモリのオーバーヘッドを回避することで、パフォーマンスが向上します。

たとえば、単一の int または bigint から成る null 許容でない列を持つクラスター化インデックス キーは、IDENTITY 句または シーケンス を使用する既定の制約で設定され、行の挿入後に更新されない場合、これらのプロパティをすべて持ちます。

逆に、1 つの uniqueidentifier 列を持つクラスター化インデックス キーは、 int に 4 バイト、 bigint に 8 バイトではなく 16 バイトのストレージを使用し、値が順番に生成されない限り、 増え続ける プロパティを満たさないため、より広くなります。

Tip

PRIMARY KEY制約を作成すると、制約をサポートする一意のインデックスが自動的に作成されます。 既定では、このインデックスはクラスター化されています。ただし、このインデックスがクラスター化インデックスの必要なプロパティを満たしていない場合は、制約を非クラスター化として作成し、代わりに別のクラスター化インデックスを作成できます。

クラスター化インデックスを作成しない場合、テーブルはヒープとして格納されます。通常は推奨されません。

クラスター化インデックスのアーキテクチャ

行ストア インデックスは、B+ ツリーとして構成されます。 インデックス B+ ツリー内の各ページをインデックス ノードと呼びます。 B+ ツリーの最上位ノードはルート ノードといいます。 インデックス内の最下位ノードをリーフ ノードと呼びます。 ルート ノードとリーフ ノードの間にあるインデックス レベルは、総称して中間レベルといいます。 クラスター化インデックスでは、リーフ ノードに基になるテーブルのデータ ページが含まれています。 ルート ノードと中間レベル ノードには、インデックス行を保持するインデックス ページが含まれています。 各インデックス行には、キー値と、B+ ツリー内の中間レベル ページかインデックスのリーフ レベルのデータ行のいずれかへのポインターが含まれています。 インデックスの各レベルのページは、二重にリンクされた一覧でリンクされています。

クラスター化インデックスは、インデックスによって使用される パーティションごとに sys.partitions に 1 つの行があり、 index_id = 1。 既定では、クラスター化インデックスのパーティションは 1 つです。 クラスター化インデックスに複数のパーティションがある場合、各パーティションには、その特定のパーティションのデータを含む個別の B+ ツリー構造があります。 たとえば、クラスター化インデックスに 4 つのパーティションがある場合、各パーティションに 1 つずつ、4 つの B+ ツリー構造があります。

クラスター化インデックスのデータ型によっては、各クラスター化インデックスの構造に 1 つ以上のアロケーション ユニットが含まれ、そこに特定のパーティションのデータが格納され、管理されます。 各クラスター化インデックスには、パーティションごとに、少なくとも 1 つの IN_ROW_DATA アロケーション ユニットがあります。 また、クラスター化インデックスには、LOB_DATA) などのラージ オブジェクト (LOB) 列が含まれている場合、パーティションごとに 1 つの割り当て単位があります。 また、8,060 バイトの行サイズ制限を超える可変長列が含まれている場合は、パーティションごとに 1 つの ROW_OVERFLOW_DATA 割り当て単位があります。

B+ ツリー構造のページは、クラスター化インデックス キーの値に基づいて順序付けられます。 すべての挿入は、挿入された行のキー値が既存のページ間の順序付けシーケンスに収まるページに対して行われます。 ページ内では、行は必ずしも物理的な順序で格納されるとは限りません。 ただし、ページでは 、スロット配列と呼ばれる内部構造を使用して行の論理的な順序が維持されます。 スロット配列内のエントリは、インデックス キーの順序で保持されます。

次の図は、1 つのパーティション内のクラスター化インデックスの構造を示します。

1 つのパーティション内のクラスター化インデックスの構造を示す図。

非クラスター化インデックスのデザイン ガイドライン

クラスター化インデックスと非クラスター化インデックスの主な違いは、非クラスター化インデックスにテーブル内の列のサブセットが含まれていることです。通常は、クラスター化インデックスとは異なる方法で並べ替えられます。 必要に応じて、非クラスター化インデックスをフィルター処理できます。つまり、テーブル内のすべての行のサブセットが含まれます。

ディスク ベースの行ストアの非クラスター化インデックスには、ベース テーブル内の行の格納場所を指す行ロケーターが含まれます。 1 つのテーブルまたはインデックス付きビューに複数の非クラスター化インデックスを作成できます。 一般に、非クラスター化インデックスは、ベース テーブルをスキャンする必要がある頻繁に使用されるクエリのパフォーマンスを向上するように設計する必要があります。

クエリ オプティマイザーでデータ値を検索するときは、本の索引を使用する場合と同じように、非クラスター化インデックスを検索してテーブル内でのデータ値の位置を探し、その位置から直接データを取得します。 非クラスター化インデックスには、クエリの検索対象であるデータ値のテーブル内での位置を正確に記述するエントリが格納されているので、完全一致比較クエリの場合は非クラスター化インデックスが最適です。

たとえば、特定のマネージャーに報告するすべての従業員に対して HumanResources.Employee テーブルに対してクエリを実行する場合、クエリ オプティマイザーは非クラスター化インデックス IX_Employee_ManagerIDを使用する場合があります。これは、最初のキー列として ManagerIDManagerID値は非クラスター化インデックスに並べ替えられます。そのため、クエリ オプティマイザーは、指定したManagerID値と一致するインデックス内のすべてのエントリをすばやく検索できます。 各インデックス エントリは、他のすべての列から対応するデータを取得できるベース テーブル内の正確なページと行を指します。 クエリ オプティマイザーは、インデックス内のすべてのエントリを検索した後、ベース テーブル全体をスキャンするのではなく、正確なページと行に直接移動してデータを取得できます。

非クラスター化インデックスのアーキテクチャ

ディスク ベースの行ストアの非クラスター化インデックスは、次の違いを除き、クラスター化インデックスと同じ B+ ツリー構造を持ちます。

  • 非クラスター化インデックスには、必ずしもテーブルのすべての列と行が含まれているとは限りません。

  • 非クラスター化インデックスのリーフ レベルは、データ ページではなくインデックス ページで構成されます。 非クラスター化インデックスのリーフ レベルのインデックス ページには、キー列が含まれています。 必要に応じて、ベース テーブルから列を取得しないように、含 まれる列としてテーブル内の他の列のサブセットを含めることもできます。

非クラスター化インデックス行の行ロケーターは、行へのポインターであるか、行のクラスター化インデックス キーです。次に説明します。

  • テーブルにクラスター化インデックスがある場合、またはインデックスがインデックス付きビューにある場合は、行ロケーターが行のクラスター化インデックス キーになります。

  • テーブルがヒープで、クラスター化インデックスが設定されていない場合、行ロケーターはその行へのポインターです。 このポインターは、ファイル識別子 (ID)、ページ番号、およびそのページ上での行の番号で構成されます。 ポインター全体は、RID (行 ID) と呼ばれます。

また、行ロケーターにより、非クラスター化インデックス行の一意性も確保されます。 次の表では、データベース エンジンが非クラスター化インデックスに行ロケーターをどのように追加するかを説明しています。

ベース テーブルの種類 非クラスター化インデックスの種類 行ロケーター
Heap
Nonunique キー列に追加される RID
Unique 付加列に追加される RID
一意のクラスター化インデックス
Nonunique キー列に追加されるクラスター化インデックス キー
Unique 付加列に追加されるクラスター化インデックス キー
一意でないクラスター化インデックス
Nonunique キー列に追加されるクラスター化インデックス キーと uniqueifier (存在する場合)
Unique 付加列に追加されるクラスター化インデックス キーと uniqueifier (存在する場合)

データベース エンジンは、指定された列を非クラスター化インデックスに複数回格納することはありません。 非クラスター化インデックスの作成時にユーザーが指定したインデックス キーの順序は常に優先されます。非クラスター化インデックスのキーに追加する必要がある行ロケーター列は、インデックス定義で指定された列の後にキーの末尾に追加されます。 非クラスター化インデックス内のクラスター化インデックス キー行ロケーターは、インデックス定義で明示的に指定されているか、暗黙的に追加されているかに関係なく、クエリ処理で使用できます。

以下の例は、行ロケーターが非クラスター化インデックスでどのように実装されるかを示しています。

クラスター化インデックス 非クラスター化インデックス定義 行ロケーターを使用した非クラスター化インデックス定義 Explanation
キー列 (ABC) を含む一意のクラスター化インデックス キー列 (BA) と付加列 (EG) を含む一意でない非クラスター化インデックス キー列 (BAC) と付加列 (EG) 非クラスター化インデックスは一意ではないので、行ロケーターはインデックス キーに存在する必要があります。 行ロケーターの列 BA はすでに存在しているので、列 C のみが追加されます。 列 C は、キー列リストの末尾に追加されます。
キー列 (A) を含む一意のクラスター化インデックス キー列 (BC) と付加列 (A) を含む一意でない非クラスター化インデックス キー列 (BCA) 非クラスター化インデックスは一意ではないので、行ロケーターはキーに追加されます。 列 A はまだキー列として指定されていないので、キー列リストの末尾に追加されます。 列 A は現在キー内にあるので、付加列として格納する必要はありません。
キー列 (AB) を含む一意のクラスター化インデックス キー列 (C) を含む一意の非クラスター化インデックス キー列 (C) と付加列 (AB) 非クラスター化インデックスは一意なので、行ロケーターは付加列に追加されます。

非クラスター化インデックスは、インデックスによって使用される パーティションごとに sys.partitions に 1 つの行があり、 index_id > 1。 既定では、非クラスター化インデックスのパーティションは 1 つです。 非クラスター化インデックスにパーティションが複数ある場合、各パーティションは、その特定のパーティションに対してインデックス行を保持する B+ ツリー構造になります。 たとえば、非クラスター化インデックスに 4 つのパーティションがある場合、各パーティションに 1 つずつ、4 つの B+ ツリー構造があります。

非クラスター化インデックスのデータ型によっては、各非クラスター化インデックスの構造に 1 つ以上のアロケーション ユニットが含まれ、そこに特定のパーティションのデータが格納され、管理されます。 少なくとも、非クラスター化インデックスには、インデックス B+ ツリー ページを格納するパーティションごとに 1 つの IN_ROW_DATA 割り当て単位があります。 また、LOB_DATA) などのラージ オブジェクト (LOB) 列が含まれている場合、非クラスター化インデックスにはパーティションごとに 1 つの割り当て単位があります。 さらに、8,060 バイトの行サイズ制限を超える可変長列が含まれている場合は、パーティションごとに 1 つの ROW_OVERFLOW_DATA 割り当て単位があります。

次の図に、1 つのパーティション内の非クラスター化インデックスの構造を示します。

1 つのパーティション内の非クラスター化インデックスの構造を示す図。

非クラスター化インデックスに含まれる列を使用する

キー列に加えて、非クラスター化インデックスでは、非キー列をリーフ レベルに格納することもできます。 これらの非キー列はインクルード列と呼ばれ、INCLUDE ステートメントのCREATE INDEX句で指定されます。

キー以外の列が含まれるインデックスは、クエリを対象とする場合、つまり、クエリで使用されるすべての列がキー列またはキー以外の列としてインデックス内にある場合に、クエリのパフォーマンスを大幅に向上させることができます。 データベース エンジンはインデックス内のすべての列値を検索できるため、パフォーマンスが向上します。ベース テーブルにアクセスできないため、ディスク I/O 操作が少なくなります。

クエリで列を取得する必要があるが、クエリ述語、集計、並べ替えで使用されない場合は、キー列としてではなく、含まれる列として追加します。 これには次の利点があります。

  • 含まれる列では、インデックス キー列として使用できないデータ型を使用できます。

  • インデックス キー列またはインデックス キー サイズの数を計算する場合、包含列はデータベース エンジンでは考慮されません。 含まれる列では、900 バイトの最大キー サイズによって制限されることはありません。 より多くのクエリに対応するより広いインデックスを作成できます。

  • インデックス キーから含まれる列に列を移動すると、インデックスの並べ替え操作が高速になるため、インデックスの作成にかかる時間が短縮されます。

テーブルにクラスター化インデックスがある場合、クラスター化インデックス キーで定義されている列は、テーブルの一意でない非クラスター化インデックスに自動的に追加されます。 非クラスター化インデックス キーまたは含まれる列で指定する必要はありません。

含まれる列を含むインデックスのガイドライン

含まれる列を使用して非クラスター化インデックスを設計する場合は、次のガイドラインを考慮してください。

  • 含まれる列は、テーブルまたはインデックス付きビューの非クラスター化インデックスでのみ定義できます。

  • textntext、および imageを除く、すべてのデータ型を使用できます。

  • 決定的な計算列、および正確または不正確な計算列を、付加列にできます。 詳細については、「計算列のインデックス」を参照してください。

  • キー列と同様に、 イメージntext、および テキスト データ型から派生した計算列は、計算列のデータ型が含まれる列で許可されている限り、列を含めることができます。

  • INCLUDE リストとキー列リストの両方に、列名を指定することはできません。

  • INCLUDE リスト内で列名を繰り返すことはできません。

  • インデックスには、少なくとも 1 つのキー列を定義する必要があります。 含まれる列の最大数は 1,023 です。 これは、テーブルの最大列数から 1 を引いた数です。

  • 含まれる列の有無に関係なく、インデックス キー列は、最大 16 個のキー列と 900 バイトの合計インデックス キー サイズの既存のインデックス サイズ制限に従う必要があります。

含まれる列を含むインデックスの設計に関する推奨事項

クエリ述語、集計、および並べ替えで使用される列のみがキー列になるように、インデックス キー サイズが大きい非クラスター化インデックスの再設計を検討してください。 クエリをカバーする他のすべての列を、非キー付加列にします。 その結果、クエリをカバーするために必要なすべての列を含むことができますが、インデックス キー自体は小さく、効率的です。

たとえば、次のクエリをカバーするインデックスを設計するとします。

SELECT AddressLine1,
       AddressLine2,
       City,
       StateProvinceID,
       PostalCode
FROM Person.Address
WHERE PostalCode BETWEEN N'98000' AND N'99999';

クエリをカバーするには、インデックスに各列を定義する必要があります。 すべての列をキー列として定義でき、その場合キーのサイズは 334 バイトになります。 検索条件に使用されている唯一の列は、30 バイトの長さの PostalCode 列なので、より効果的な設計のインデックスにするには、キー列として PostalCode を定義し、他のすべての列を非キー列として含めます。

次のステートメントにより、クエリをカバーする付加列インデックスが作成されます。

CREATE INDEX IX_Address_PostalCode
ON Person.Address (PostalCode)
    INCLUDE (AddressLine1, AddressLine2, City, StateProvinceID);

インデックスがクエリをカバーしていることを検証するには、インデックスを作成し、推定実行プランを表示します。 実行プランに インデックスの IX_Address_PostalCode 演算子が表示されている場合、クエリはインデックスによってカバーされます。

列が含まれるインデックスのパフォーマンスに関する考慮事項

含まれる列の数が非常に多いインデックスは作成しないでください。 インデックスがより多くのクエリを対象にしている場合でも、パフォーマンス上の利点は次の理由で減少します。

  • 1 ページに収まるインデックス行が少なくなります。 これにより、ディスク I/O が増加し、キャッシュの効率が低下します。

  • インデックスを格納するために、さらに多くのディスク領域が必要になります。 特に、含まれる列 に varchar(max)nvarchar(max)varbinary(max)、または xml データ型を追加すると、ディスク領域の要件が大幅に増加する可能性があります。 これは、列の値がインデックスのリーフ レベルにコピーされるためです。 そのため、列の値がインデックスとベース テーブルの両方に存在します。

  • ベース テーブルと非クラスター化インデックスの両方で多くの列を変更する必要があるため、データ変更のパフォーマンスが低下します。

クエリ パフォーマンスの向上が、データ変更のパフォーマンスの低下とディスク領域の要件の増加を上回るかどうかを判断する必要があります。

一意インデックスのデザイン ガイドライン

一意のインデックスは、インデックス キーに重複する値が含まれていないことを保証します。 一意インデックスの作成は、一意性がデータ自体の特性である場合にのみ可能です。 たとえば、主キーが NationalIDNumber で、HumanResources.Employee テーブルの EmployeeID 列の値が必ず一意になるようにする場合は、UNIQUE 列で NationalIDNumber 制約を作成します。 この制約では、重複する国内 ID 番号を持つ行を導入しようとする試みが拒否されます。

複数列に一意インデックスを指定すると、インデックス キーの値の組み合わせはそれぞれ一意になります。 たとえば、 LastNameFirstName、および MiddleName 列の組み合わせで一意のインデックスが作成された場合、テーブル内の 2 つの行でこれらの列に同じ値を持つ必要はありません。

クラスター化インデックスと非クラスター化インデックスは共に一意インデックスにできます。 一意のクラスター化インデックスと、同じテーブル上に複数の一意の非クラスター化インデックスを作成できます。

一意のインデックスの利点は次のとおりです。

  • データの一意性を必要とするビジネス ルールが適用されます。
  • クエリ オプティマイザーの役に立つ追加情報が提供されます。

PRIMARY KEY 制約または UNIQUE 制約を作成すると、指定した列に一意インデックスが自動的に作成されます。 UNIQUE 制約を作成することと、制約とは無関係の一意なインデックスを作成することの間に大きな違いはありません。 データ検証も同じ方式で行われ、クエリ オプティマイザーでは、制約によって作成された一意インデックスと手動で作成された一意インデックスは区別されません。 ただし、ビジネス ルールの適用が目標である場合は、列に UNIQUE 制約または PRIMARY KEY 制約を作成する必要があります。 この作業を行うことで、インデックスの目的が明確になります。

一意のインデックスに関する考慮事項

  • 重複するキー値がデータに存在する場合は、一意なインデックス、UNIQUE 制約、または PRIMARY KEY 制約を作成できません。

  • データが一意のときに一意性を強制する場合は、一意インデックスを作成する方が、同じ組み合わせの列に一意でないインデックスを作成するよりも、より効率的な実行プランを作成できる追加情報がクエリ オプティマイザーに提供されます。 この場合は、 UNIQUE 制約または一意のインデックスを作成することをお勧めします。

  • 一意非クラスター化インデックスには、付加非キー列を含めることができます。 詳細については、「 非クラスター化インデックスに含まれる列を使用する」を参照してください。

  • PRIMARY KEY制約とは異なり、UNIQUE制約または一意のインデックスは、インデックス キーに null 許容列を使用して作成できます。 一意性の適用のために、2 つの NULL が等しいと見なされます。 たとえば、単一列の一意のインデックスでは、テーブル内の 1 つの行に対してのみ列を NULL にすることができます。

フィルター選択されたインデックスのデザイン ガイドライン

フィルター選択されたインデックスは、最適化された非クラスター化インデックスであり、テーブル内のデータの小さなサブセットを必要とするクエリに特に適しています。 インデックス定義のフィルター述語を使用して、テーブル内の行の一部にインデックスを付けます。 適切に設計されたフィルター選択されたインデックスを使用すると、完全テーブル インデックスと比較して、クエリのパフォーマンスが向上し、インデックスの更新コストが削減され、インデックスのストレージ コストが削減されます。

フィルター選択されたインデックスは、テーブル全体のインデックスよりも次の点で優れています。

  • クエリのパフォーマンスとプランの品質の向上

    適切に設計されたフィルター選択インデックスは、テーブル全体の非クラスター化インデックスよりも小さいため、クエリのパフォーマンスと実行プランの品質が向上します。 フィルターインデックスは、そのインデックス内の行のみを対象にしているため、完全なテーブルの統計情報よりも正確な統計です。

  • インデックスの更新コストの削減

    インデックスは、データ操作言語 (DML) ステートメントがインデックス内のデータに影響を与える場合にのみ更新されます。 フィルター選択されたインデックスは、テーブル全体の非クラスター化インデックスと比較してインデックスの更新コストが削減されます。これは、インデックス内のデータが影響を受けた場合にのみ更新されるためです。 特に、含まれるデータにほとんど影響がない場合は、多数のフィルター選択されたインデックスを作成できます。 同様に、フィルター選択されたインデックスに、頻繁に影響を受けるデータのみが含まれている場合、インデックスのサイズが小さいと、統計の更新コストが削減されます。

  • インデックスのストレージ コストの削減

    テーブル全体のインデックスが不要な場合は、フィルター選択されたインデックスを作成すると、非クラスター化インデックスのディスク ストレージを削減できます。 ストレージ要件を大幅に増やすことなく、完全テーブルの非クラスター化インデックスを複数のフィルター選択されたインデックスに置き換えることができる場合があります。

フィルター選択されたインデックスは、列にデータの適切に定義されたサブセットが含まれている場合に便利です。 例を次に示します。

  • 多数の NULL を含む列。

  • 複数のカテゴリのデータを含む異種列。

  • 量、時刻、日付などの値の範囲を含む列。

フィルター選択されたインデックスの更新コストの削減は、インデックス内の行数がテーブル全体のインデックスと比較して少ない場合に最も顕著です。 フィルター選択されたインデックスにテーブル内のほとんどの行が含まれる場合は、テーブル全体のインデックスよりもメンテナンス コストがかかることがあります。 この場合は、フィルター選択されたインデックスではなく、テーブル全体のインデックスを使用する必要があります。

フィルター選択されたインデックスは 1 つのテーブルで定義され、単純な比較演算子のみをサポートします。 複雑なロジックを持つフィルター式が必要な場合、または複数のテーブルを参照する場合は、 インデックス付きの計算列 または インデックス付きビューを作成する必要があります。

フィルター付きインデックスの設計に関する考慮事項

フィルター選択されたインデックスを効果的にデザインするには、アプリケーションで使用されるクエリを把握し、そのクエリがデータのサブセットとどのように関連するかを理解することが重要です。 適切に定義されたサブセットを持つデータの例としては、多くの NULL を含む列、異なるカテゴリの値を持つ列、異なる値の範囲を持つ列があります。

次の設計上の考慮事項は、フィルター選択されたインデックスがテーブル全体のインデックスよりも優れた利点を提供できる場合のいくつかのシナリオを示しています。

データのサブセットのフィルター選択されたインデックス

クエリに関連する少数の値だけが列に含まれている場合、値のサブセットにフィルター選択されたインデックスを作成できます。 たとえば、列がほとんど NULL で、クエリに NULL 以外の値のみが必要な場合は、NULL 以外の行を含むフィルター選択されたインデックスを作成できます。

たとえば、AdventureWorks サンプル データベースには、2,679 行の Production.BillOfMaterials テーブルがあります。 EndDate列には NULL 以外の値を含む行が 199 行のみあり、他の 2480 行には NULL が含まれています。 次のフィルター選択されたインデックスは、インデックスで定義された列を返し、 EndDateに NULL 以外の値を持つ行のみを必要とするクエリについて説明します。

CREATE NONCLUSTERED INDEX FIBillOfMaterialsWithEndDate
ON Production.BillOfMaterials (ComponentID, StartDate)
    WHERE EndDate IS NOT NULL;

フィルター選択されたインデックス FIBillOfMaterialsWithEndDate は、次のクエリに対して有効です。 推定実行プランを表示して、クエリ オプティマイザーでフィルター選択されたインデックスが使用されたかどうかを確認します。

SELECT ProductAssemblyID,
       ComponentID,
       StartDate
FROM Production.BillOfMaterials
WHERE EndDate IS NOT NULL
      AND ComponentID = 5
      AND StartDate > '20080101';

フィルター選択されたインデックスの作成方法およびフィルター選択されたインデックスの述語式の定義方法の詳細については、「フィルター選択されたインデックスの作成」を参照してください。

異種データのフィルター選択されたインデックス

テーブルに異種データの行が含まれている場合、1 つ以上のカテゴリのデータに対してフィルター選択されたインデックスを作成できます。

たとえば、 Production.Product テーブルに示される製品がそれぞれ ProductSubcategoryIDに割り当てられ、Bikes、Components、Clothing、Accessories の製品カテゴリに関連付けられています。 Production.Product テーブル内にあるこうしたカテゴリの列の値はあまり密接に関連していないので、異種カテゴリとなります。 たとえば、 ColorReorderPointListPriceWeightClass、および Style の各列には、各製品カテゴリで固有の特性があります。 サブカテゴリ 27 ~ 36 を含む付属品に対して頻繁に使用されるクエリがあるとします。 次の例に示すように、付属品のサブカテゴリにフィルター選択されたインデックスを作成することで、付属品に対するクエリのパフォーマンスを向上させることができます。

CREATE NONCLUSTERED INDEX FIProductAccessories
ON Production.Product (ProductSubcategoryID, ListPrice)
INCLUDE (Name)
WHERE ProductSubcategoryID >= 27 AND ProductSubcategoryID <= 36;

フィルター選択されたインデックス FIProductAccessories は、クエリ結果がインデックスに含まれており、クエリ プランがベース テーブルにアクセスする必要がないため、次のクエリを対象とします。 たとえば、クエリ述語式 ProductSubcategoryID = 33 はフィルター選択されたインデックスの述語 ProductSubcategoryID >= 27 および ProductSubcategoryID <= 36のサブセットで、クエリ述語の ProductSubcategoryID 列と ListPrice 列はどちらもインデックスのキー列であり、名前は付加列としてインデックスのリーフ レベルに格納されます。

SELECT Name,
       ProductSubcategoryID,
       ListPrice
FROM Production.Product
WHERE ProductSubcategoryID = 33
      AND ListPrice > 25.00;

フィルターインデックス内のキー列と含められた列

クエリ オプティマイザーがクエリ実行プランのフィルター選択されたインデックスを選択する必要がある場合にのみ、フィルター選択されたインデックス定義に少数の列を追加することをお勧めします。 クエリ オプティマイザーでは、フィルター選択されたインデックスがクエリに対応するかどうかに関係なく、フィルター選択されたインデックスがクエリに対して選択されます。 ただし、フィルター選択されたインデックスがクエリに対応する場合は、そのインデックスが選択される可能性は高くなります。

場合によっては、フィルター選択されたインデックスは、その式の列をキー列または付加列としてフィルター選択されたインデックスの定義に含めなくても、クエリに対応します。 次のガイドラインでは、フィルター選択されたインデックスの式の列をフィルター選択されたインデックスの定義でキー列または付加列にする必要がある場合について説明します。 次の例では、以前に作成したフィルター選択されたインデックス FIBillOfMaterialsWithEndDate を使用します。

フィルター選択されたインデックスの式がクエリ述語と同じであり、フィルター選択されたインデックスの式の列がクエリ結果と共に返されない場合、その式の列を、フィルター選択されたインデックスの定義でキー列または付加列にする必要はありません。 たとえば、クエリ述語がフィルター式と同じであり、 FIBillOfMaterialsWithEndDate がクエリ結果と共に返されないため、 EndDate は次のクエリに対応します。 FIBillOfMaterialsWithEndDateインデックスは、フィルター済みインデックス定義におけるキーまたは含まれる列としてEndDateである必要はありません。

SELECT ComponentID,
       StartDate
FROM Production.BillOfMaterials
WHERE EndDate IS NOT NULL;

フィルター選択されたインデックスの式と異なるクエリ述語で比較に列が使用される場合は、フィルター選択されたインデックスの式の列を、フィルター選択されたインデックスの定義でキー列または付加列にする必要があります。 たとえば、 FIBillOfMaterialsWithEndDate は、フィルター選択されたインデックスから行のサブセットを選択するので、次のクエリに対して有効です。 ただし、 EndDate が比較 EndDate > '20040101'で使用されるため、次のクエリには対応していません。この比較は、フィルター選択されたインデックスの式と異なります。 クエリ プロセッサは、 EndDateの値を調べなければ、このクエリを実行できません。 したがって、 EndDate をフィルター選択されたインデックスの定義でキー列または付加列にする必要があります。

SELECT ComponentID,
       StartDate
FROM Production.BillOfMaterials
WHERE EndDate > '20040101';

フィルター選択されたインデックスの式の列がクエリ結果セットに含まれる場合、その列をフィルター選択されたインデックスの定義でキー列または付加列にする必要があります。 たとえば、 FIBillOfMaterialsWithEndDate はクエリ結果に含まれる EndDate 列を返すので、次のクエリに対応しません。 したがって、 EndDate をフィルター選択されたインデックスの定義でキー列または付加列にする必要があります。

SELECT ComponentID,
       StartDate,
       EndDate
FROM Production.BillOfMaterials
WHERE EndDate IS NOT NULL;

テーブルのクラスター化インデックス キーは、フィルター選択されたインデックスの定義でキー列または付加列にする必要はありません。 クラスター化インデックス キーは、フィルター選択されたインデックスなど、すべての非クラスター化インデックスに自動的に含まれます。

フィルター述語のデータ変換演算子

フィルター選択されたインデックスでは、その式に指定された比較演算子によって暗黙的または明示的なデータ変換が行われる場合、変換が比較演算子の左辺で行われると、エラーが発生します。 解決方法としては、比較演算子の右辺にデータ変換演算子 (CAST または CONVERT) を含む、フィルター選択されたインデックスの式を記述します。

次の例では、異なるデータ型の列を含むテーブルを作成します。

CREATE TABLE dbo.TestTable
(
    a INT,
    b VARBINARY(4)
);

次のフィルター選択されたインデックス定義では、列 b は、定数 1 と比較するために整数データ型に暗黙的に変換されます。 これにより、フィルター選択された述語の演算子の左辺で変換が行われるため、エラー メッセージ 10611 が生成されます。

CREATE NONCLUSTERED INDEX TestTabIndex
ON dbo.TestTable (a, b)
    WHERE b = 1;

解決策として、次の例に示すように、右辺の定数を、列 b と同じ型になるように変換します。

CREATE INDEX TestTabIndex
ON dbo.TestTable (a, b)
    WHERE b = CONVERT (VARBINARY(4), 1);

データ変換を比較演算子の左辺から右辺に移動すると、変換の意味が変わることがあります。 前の例では、 CONVERT 演算子が右側に追加されたときに、比較が int 比較から varbinary 比較に変更されました。

列ストア インデックスのアーキテクチャ

列ストア インデックスは、列ストアと呼ばれる列形式のデータ形式を使用してデータを格納、取得、および管理するためのテクノロジです。 詳細については、「 列ストア インデックス: 概要」を参照してください。

バージョン情報と新機能については、「列ストア インデックスの新機能」を参照してください。

これらの基本を知ることで、この技術を効果的に利用する方法を説明している他のコラムストアに関する記事が理解しやすくなります。

データ ストレージで列ストアと行ストアを使用する

列ストア インデックスの説明では、データ ストレージの形式を強調する目的で行ストア列ストアという用語を使用しています。 列ストア インデックスでは、両方の種類のストレージを使用します。

クラスター化列ストア インデックスの図

  • 列ストア は、行と列を含むテーブルとして論理的に編成され、列方向のデータ形式で物理的に格納されているデータです。

    列ストア インデックスでは、ほとんどのデータを列ストア形式で物理的に格納します。 列ストア形式では、データは列として圧縮および非圧縮されます。 クエリで要求されない行ごとに、その他の値を非圧縮する必要はありません。 このため、大規模なテーブルの列全体を高速にスキャンできます。

  • 行ストア は、行と列を含むテーブルとして論理的に編成され、行方向のデータ形式で物理的に格納されているデータです。 これは、クラスター化された B+ ツリー インデックスやヒープなどのリレーショナル テーブル データを格納する従来の方法でした。

    列ストア インデックスでは、 デルタストアと呼ばれる行ストア形式の行も物理的に格納されます。 デルタストア (デルタ行グループとも呼ばれます) は、列ストアへの圧縮に適合させるために、数が少なすぎる行を格納する場所です。 各デルタ行グループは、クラスター化された B+ ツリー インデックス (行ストア) として実装されます。

操作は行グループと列セグメント上で実行される

列ストア インデックスでは、行を管理可能な単位にグループ化します。 これらの単位はそれぞれ、行グループと呼ばれます。 最適なパフォーマンスを得るために、行グループ内の行数は、圧縮率を向上させるのに十分な大きさであり、メモリ操作でメリットを得るのに十分な小ささです。

たとえば、列ストア インデックスは、行グループで次の操作を実行します。

  • 行グループを列ストアに圧縮します。 圧縮は、行グループ内の各列セグメントで実行されます。

  • 削除されたデータの削除など、 ALTER INDEX ... REORGANIZE 操作中に行グループをマージします。

  • ALTER INDEX ... REBUILD操作中にすべての行グループを再作成します。

  • 動的管理ビュー (DMV) の行グループの正常性と断片化に関するレポートを行います。

デルタストアは、デルタ行グループと呼ばれる 1 つ以上の行グループで構成されます。 各デルタ行グループは、小さな一括読み込みを格納し、行グループに 1,048,576 行が含まれるまで挿入するクラスター化された B+ ツリー インデックスです。この場合、 タプル ムーバー と呼ばれるプロセスによって閉じられた行グループが列ストアに自動的に圧縮されます。

行グループの状態の詳細については、「sys.dm_db_column_store_row_group_physical_stats」を参照してください。

Tip

小さな行グループが多すぎると、列ストア インデックスの品質が低下します。 再編成操作を実行すると、削除された行を削除して圧縮された行グループを結合する方法を決定する内部しきい値ポリシーに従って、小さな行グループがマージされます。 マージ後、インデックスの品質が向上します。

SQL Server 2019 (15.x) 以降のバージョンでは、組ムーバーは、内部のしきい値によって一定の時間存在していた小さい開いているデルタ行グループを自動的に圧縮したり、多数の行が削除された圧縮された行グループをマージしたりするバックグラウンド マージ タスクによって役立ちます。

それぞれの列には、行グループごとにその値の一部が含まれます。 これらの値は列セグメントと呼ばれます。 それぞれの行グループには、テーブルの 1 つの列につき 1 つの列セグメントが含まれます。 それぞれの列には、行グループごとに 1 つの列セグメントがあります。

クラスター化列ストア列セグメントの図。

列ストア インデックスが行グループを圧縮する場合、各列セグメントを個別に圧縮します。 列全体を非圧縮する場合、列ストア インデックスでは、それぞれの行グループから列セグメントを 1 つ非圧縮するだけで列全体を非圧縮できます。

小規模の読み込みと挿入はデルタストアに移動される

列ストア インデックスは、一度に少なくとも 102,400 個の行を列ストア インデックスに圧縮することで、列ストア インデックスの圧縮とパフォーマンスを向上させています。 行を一括で圧縮するために、列ストア インデックスでは、小規模な読み込みを累積し、デルタストアに挿入します。 デルタストア操作はバックグラウンドで処理されます。 クエリ結果を返すために、クラスター化列ストア インデックスは、列ストアとデルタストアの両方からのクエリ結果を結合します。

次の場合に、行はデルタストアに移動されます。

  • INSERT INTO ... VALUES ステートメントで挿入された場合。

  • 一括読み込みの最後で 102,400 未満の場合。

  • Updated. 更新はそれぞれ、削除および挿入として実装されます。

また、デルタストアでは、削除済みとしてマークされているが、列ストアから物理的に削除されていない、削除された行の ID の一覧も格納します。

デルタ行グループは、満杯になると列ストアに圧縮される

クラスター化列ストア インデックスでは、デルタ行グループごとに最大 1,048,576 個の列を収集してから、行グループを列ストアに圧縮します。 これにより、列ストア インデックスの圧縮が向上します。 デルタ行グループが行数の上限に達すると、OPEN 状態から CLOSED 状態に移行します。 組ムーバーというバックグラウンド プロセスによって、閉じられた行グループがチェックされます。 プロセスによって閉じている行グループが見つけられると、その行グループは圧縮され、列ストアに格納されます。

デルタ行グループが圧縮されると、既存のデルタ行グループは、参照がない場合は組ムーバーによって後で削除される TOMBSTONE 状態に移行します。また、新しい圧縮行グループは COMPRESSED とマークされます。

行グループの状態の詳細については、「sys.dm_db_column_store_row_group_physical_stats」を参照してください。

インデックスを再構築または再構成するには、ALTER INDEX を使用してデルタ行グループを列ストアに強制的に圧縮することができます。 圧縮中にメモリ負荷がある場合、列ストア インデックスは圧縮行グループ内の行数を減らす可能性があります。

各テーブル パーティションには、独自の行グループとデルタ行グループが含まれる

パーティション分割の概念は、クラスター化インデックス、ヒープ、列ストア インデックスでも同じです。 テーブルのパーティション分割では、列の値の範囲に従って、テーブルをより小規模の列のグループに分割します。 通常、これはデータを管理するために使用されます。 たとえば、データの年ごとにパーティションを作成し、パーティション切り替えを使用して古いデータを低コストのストレージにアーカイブできます。

行グループは常に、テーブル パーティション内に定義されます。 列ストア インデックスがパーティション分割されると、各パーティションには独自の圧縮行グループとデルタ行グループが含まれます。 パーティション分割されていないテーブルには、1 つのパーティションが含まれています。

Tip

列ストアからデータを削除する必要がある場合は、テーブルのパーティション分割の使用を検討してください。 不要になったパーティションの切り替えと切り捨ては、列ストアで断片化を発生させずにデータを削除する効率的な方法です。

各パーティションに複数のデルタ行グループを含めることができる

各パーティションに複数のデルタ行グループを含めることができます。 列ストア インデックスがデルタ行グループにデータを追加する必要があり、デルタ行グループが別のトランザクションによってロックされている場合、列ストア インデックスは別のデルタ行グループのロックを取得しようとします。 使用できるデルタ行グループがない場合は、列ストア インデックスでは新しいデルタ行グループが作成されます。 たとえば、パーティションが 10 個のテーブルには、簡単に 20 個以上のデルタ行グループを含めることができます。

同じテーブルで列ストア インデックスと行ストア インデックスを結合する

非クラスター化インデックスには、基になるテーブルの行と列の一部または全体のコピーが含まれています。 インデックスはテーブルの 1 つ以上の列として定義され、行のフィルター処理条件をオプションで設定できます。

更新可能な非クラスター化列ストア インデックスを、行ストア テーブルに作成できます。 列ストア インデックスは、データのコピーを格納するため、追加のストレージが必要です。 ただし、列ストア インデックス内のデータは、行ストア テーブルに必要なサイズよりもはるかに小さいサイズに圧縮されます。 これを行うことで、行ストア インデックスの列ストア インデックスと OLTP ワークロードに対して同時に分析を実行できます。 行ストア テーブルでデータが変更されると列ストアが更新されるため、両方のインデックスが同じデータに対して作業を実行します。

行ストア テーブルには、非クラスター化列ストア インデックスを 1 つ含めることができます。 詳細については、「 列ストア インデックス - 設計ガイダンス」を参照してください。

クラスター化列ストア テーブルには、1 つ以上の非クラスター化行ストア インデックスを含めることができます。 これにより、基になる列ストアで、効率的なテーブル シークを実行できます。 他のオプションも使用できます。 たとえば、行ストア テーブルに UNIQUE 制約を使用して一意性を適用できます。 一様でない値が行ストア テーブルに挿入できない場合、データベース エンジンは列ストアにも値を挿入しません。

非クラスター化列ストアのパフォーマンスに関する考慮事項

非クラスター化列ストア インデックスの定義で、フィルター適用条件の使用をサポートします。 列ストア インデックスの追加によるパフォーマンスの影響を最小限に抑えるには、フィルター式を使用して、分析に必要なデータのサブセットのみに非クラスター化列ストア インデックスを作成します。

メモリ最適化テーブルには、1 つの列ストア インデックスを含めることができます。 テーブルの作成時に作成することも、 後で ALTER TABLE を使用して追加することもできます。

詳細については、「 列ストア インデックス - クエリパフォーマンス」を参照してください。

メモリ最適化ハッシュ インデックスの設計ガイドライン

In-Memory OLTP を使用する場合、すべてのメモリ最適化テーブルに少なくとも 1 つのインデックスが必要です。 メモリ最適化テーブルの場合、すべてのインデックスもメモリ最適化されます。 ハッシュ インデックスは、メモリ最適化テーブルで使用できるインデックスの種類の 1 つです。 詳細については、「メモリ最適化テーブルのインデックス」を参照してください。

メモリ最適化ハッシュ インデックス アーキテクチャ

ハッシュ インデックスはポインターの配列で構成され、その配列の各要素はハッシュ バケットと呼ばれます。

  • 各バケットは 8 バイトであり、キー エントリのリンク リストのメモリ アドレスを格納するために使用されます。
  • 各エントリは、インデックス キーの値と、基になるメモリ最適化テーブル内の対応する行のアドレスです。
  • 各エントリは、すべて現在のバケットにチェーンされたエントリのリンク リスト内の次のエントリを指します。

バケットの数は、インデックス作成時に指定する必要があります。

  • テーブルの行数または個別の値の数に対するバケット数の割合が低ければ低いほど、バケットの平均リンク リストは長くなります。
  • 短いリンク リストは、長いリンク リストよりも高速で実行されます。
  • ハッシュ インデックスのバケットの最大数は 1,073,741,824 です。

Tip

データの適切な BUCKET_COUNT を決定するには、ハッシュ インデックスのバケット数の構成を参照してください。

ハッシュ関数はインデックス キー列に適用され、関数の結果によってキーがどのバケットに分類されるかが決まります。 各バケットには、ハッシュされたキー値がそのバケットにマップされている行へのポインターがあります。

ハッシュ インデックスに使用するハッシュ関数には、以下の特徴があります。

  • データベース エンジンには、すべてのハッシュ インデックスに使用されるハッシュ関数が 1 つ用意されています。
  • ハッシュ関数は決定的です。 入力キー値が同じであれば、常にハッシュ インデックスの同じバケットにマッピングされます。
  • インデックス キーが違っても、同じハッシュ バケットにマッピングされることがあります。
  • ハッシュ関数はバランスが取られます。つまり、通常、ハッシュ バケット上のインデックス キー値の分布は、平坦な線形分布ではなくポアソン分布またはベル カーブ分布に従います。
  • ポアソン分布は均等な分布ではありません。 インデックス キーの値は、ハッシュ バケットで均等に分散されません。
  • 2 つのインデックス キーが同じハッシュ バケットにマッピングされた場合には、ハッシュの競合となります。 ハッシュの競合が多い場合、読み取り操作にパフォーマンスが影響する可能性があります。 現実的な目標は、バケットの 30% に 2 つの異なるキー値が含まれていることです。

ハッシュ インデックスとバケットの関係をまとめると、次の図のようになります。

ハッシュ インデックスとバケット間の相互作用を示す図。

ハッシュ インデックス バケット数を構成する

ハッシュ インデックスのバケット数はインデックス作成時に指定しますが、ALTER TABLE...ALTER INDEX REBUILD 構文を使用して変更することができます。

ほとんどの場合、バケット数はインデックス キー内の個別の値の数の 1 ~ 2 倍にする必要があります。 特定のインデックス キーに含まれる値の数を常に予測できるとは限りません。 BUCKET_COUNT値がキー値の実際の数の 10 倍以内であれば、パフォーマンスは通常まだ良好であり、低く見積もるよりは多く見積もりすぎるほうが一般的によい結果が得られます。

少なすぎるバケットには、次の短所があります。

  • 個別のキー値のハッシュの競合の増加。
  • 個別の値が、異なる個別の値を持つバケットの共有を強いられます。
  • パケットごとの平均チェーン長が増えます。
  • バケット チェーンが長ければ長いほど、インデックスでの等値検索の速度が遅くなります。

多すぎるバケットには、次の短所があります。

  • バケット数が高すぎると、空のバケットを増やす結果になることがあります。
  • 空のバケットは、フル インデックス スキャンのパフォーマンスに影響を与えます。 スキャンが普通に行われる場合は、インデックス キーの個別の値の数に近いバケット数を選択することを検討してください。
  • 空のバケットは、それぞれが使用するのはわずか 8 バイトですが、メモリを使用します。

Note

バケットを追加しても、重複する値を共有するエントリのチェーンが短くなることはありません。 値の重複率は、バケット数を計算するのではなく、ハッシュ インデックスと非クラスター化インデックスのどちらを適切なインデックスの種類にするかを決定するために使用されます。

ハッシュ インデックスのパフォーマンスに関する考慮事項

ハッシュ インデックスのパフォーマンスは次のようになります。

  • WHERE 句の述語で、ハッシュ インデックス キーの各列の正確な値を指定する場合は極めて良好です。 ハッシュ インデックスは、非等値述語が指定されているとスキャンに戻ります。
  • WHERE 句の述語でインデックス キーの値の範囲を探す場合は、よくありません。
  • WHERE 句の述語で、2 列のハッシュ インデックス キーの最初の列について特定の値を指定し、キーのの列については値を指定しない場合は、よくありません。

Tip

述語には、ハッシュ インデックス キー のすべての 列を含める必要があります。 ハッシュインデックスでは、インデックスをシークするためにキー全体が必要です。

ハッシュ インデックスが使用され、一意のインデックス キーの数が行数の 100 倍を超える場合は、大きな行チェーンを回避するためにバケット数を大きくするか、代わりに 非クラスター化インデックス を使用することを検討してください。

ハッシュ インデックスを作成する

ハッシュ インデックスを作成する場合は、次の点を考慮してください。

  • ハッシュ インデックスは、メモリ最適化テーブルにのみ存在できます。 これはディスク ベース テーブルには存在できません。
  • ハッシュ インデックスは既定では一意ではありませんが、一意として宣言できます。

次の例では、一意のハッシュ インデックスを作成します。

ALTER TABLE MyTable_memop ADD INDEX ix_hash_Column2
    UNIQUE HASH (Column2) WITH (BUCKET_COUNT = 64);

メモリ最適化テーブルにおける行バージョンとガベージコレクション

メモリ最適化テーブルでは、行が UPDATE ステートメントの影響を受けると、テーブルによって行の更新バージョンが作成されます。 更新トランザクションの間、他のセッションは行の前のバージョンを読み取ることができるため、行ロックに関連するパフォーマンスの低下を回避することができます。

ハッシュ インデックスに、更新に対応するための異なるバージョンのエントリも存在することがあります。

後で前のバージョンが不要になったときに、ガベージ コレクション (GC) スレッドがバケットとそのリンク リストを横断して、前のエントリをクリーンアップします。 GC スレッドのパフォーマンスは、リンク リストのチェーン長が短い場合に優れています。 詳細については、「インメモリ OLTP ガベージ コレクション」を参照してください。

メモリ最適化された非クラスター化インデックスのデザイン ガイドライン

ハッシュ インデックスに加えて、非クラスター化インデックスは、メモリ最適化テーブル内の他の可能なインデックス型です。 詳細については、「メモリ最適化テーブルのインデックス」を参照してください。

メモリ最適化非クラスター化インデックス アーキテクチャ

メモリ最適化テーブルの非クラスター化インデックスは、最初は 2011 年に Microsoft Research によって想定および記述された Bw ツリーと呼ばれるデータ構造を使用して実装されます。 Bw ツリーは、B ツリーのロックおよびラッチフリーのバリエーションです。 詳細については、「Bw ツリー: 新しいハードウェア プラットフォーム向けの B ツリー」を参照してください。

大まかに言えば、Bw ツリーは、ページ ID (PidMap) 別に編成されたページのマップ、ページ ID (PidAlloc) を割り当てて再利用するための機能、およびページ マップ内と相互にリンクされたページのセットとして理解できます。 これら 3 つの上位レベルのサブコンポーネントが、Bw ツリーの基本的な内部構造を構成します。

その構造は、各ページに並べ替えられたキー値のセットがあり、インデックス内にそれぞれが下位レベルを示すレベルがあり、リーフ レベルがデータ行を示すという点で、通常の B ツリーと似ています。 ただし、違いもいくつかあります。

ハッシュ インデックスと同様に、複数のデータ行をリンクしてバージョン管理をサポートできます。 レベル間のページ ポインターは論理ページ ID です。これは、ページ マッピング テーブルのオフセットなので、各ページの物理アドレスがあります。

インデックス ページのインプレース更新はありません。 この目的のために新しいデルタ ページが導入されています。

  • ページの更新のためにラッチやロックは必要ありません。
  • インデックス ページは固定サイズではありません。

非葉ノードレベルの各ページのキー値は、指し示す子ノードが持つ最も高い値であり、各行にはそのページの論理ページIDも含まれます。 リーフレベルのページには、キー値と共に、データ行の物理アドレスが含まれています。

ポイント参照は B ツリーに似ていますが、ページは 1 方向にのみリンクされるため、データベース エンジンは右のページ ポインターに従います。この場合、各非リーフ ページは、B ツリーのように最小値ではなく、子の値が最も高くなります。

リーフ レベルのページを変更する必要がある場合、データベース エンジンはページ自体を変更しません。 代わりに、データベース エンジンは変更を記述するデルタ レコードを作成し、前のページに追加します。 次に、前のページのページ マップ テーブル アドレスが、このページの物理アドレスになる差分レコードのアドレスに更新されます。

Bw ツリーの構造を管理するために必要な 3 つの操作があります。統合、分割、マージです。

差分統合

デルタ レコードの長いチェーンでは、インデックスを検索するときに長いチェーン トラバーサルが必要になる可能性があるため、最終的に検索パフォーマンスが低下する可能性があります。 要素数が既に 16 個のチェーンに新しい差分レコードが追加された場合、差分レコードの変更は参照されるインデックス ページに統合され、統合をトリガーした新しい差分レコードに示される変更を含むページが再構築されます。 新しく構築されたページのページ ID は同じですが、メモリ アドレスは新しくなります。

メモリ最適化ページ マッピング テーブルを示す図。

分割ページ

Bw ツリーのインデックス ページは、1 行の格納から最大 8 KB の格納まで必要に応じてサイズが大きくなります。 インデックス ページのサイズが 8 KB まで大きくなった後に新しく 1 行追加されると、インデックス ページは分割されます。 内部ページの場合は、別のキー値とポインターを追加する余地がなくなり、リーフ ページの場合は、すべての差分レコードを組み込んだ後に行のサイズが大きすぎてページに収まらなくなることを意味します。 リーフ ページのページ ヘッダーの統計情報は、差分レコードを統合するために必要な容量を追跡します。 この情報は、新しいデルタ レコードが追加されるたびに調整されます。

分割操作は、2 つのアトミック手順で実行されます。 次のダイアグラムでは、値が 5 のキーが挿入されるため、リーフページで分割が強制実行されます。現在のリーフレベル ページの末尾を示す非リーフページ (キー値 4) が存在しなくなります。

メモリ最適化インデックス分割操作を示す図。

手順 1:P1P2 という新しい 2 ページを割り当て、新しく挿入された行を含め、以前の P1 ページの行をこれらの新しいページに分割します。 ページ マッピング テーブルの新しいスロットは、ページ P2 の物理アドレスを格納するために使用されます。 ページ P1P2 には、同時操作にまだアクセスできません。 さらに、P1 から P2 への論理ポインターがセットされます。 次に、1 つのアトミック手順でページ マッピング テーブルが更新され、ポインターが古い P1 から新しい P1 に変更されます。

手順 2: 非リーフ ページは P1 を指しますが、非リーフ ページから P2 への直接ポインターはありません。 P2P1 を介してのみ到達可能です。 非リーフ ページから P2 へのポインターを作成するには、新しい非リーフ ページ (内部インデックス ページ) を割り当て、古い非リーフ ページのすべての行をコピーし、P2 を示す新しい行を追加します。 この手順が完了したら、1 つのアトミック手順で、ページ マッピング テーブルを更新して、ポインターを古い非リーフ ページから新しい非リーフ ページに変更します。

マージページ

DELETE操作の結果、ページの最大ページ サイズ (8 KB) の 10% 未満、または 1 行が含まれる場合、そのページは連続したページとマージされます。

ページから行が削除されると、その削除の差分データが追加されます。 さらに、インデックス ページ (非リーフ ページ) がマージの対象かどうかを判断するチェックが行われます。 この確認で、行を削除した後の残領域が最大ページ サイズの 10パーセント未満になるかどうかが検証されます。 条件が満たされた場合、マージは 3 つのアトミックステップで実行されます。

次の図では、DELETE 操作でキー値 10 が削除されています。

メモリ最適化インデックスのマージ操作を示す図。

手順 1: キー値 10 (青色の三角形) を表す差分ページが作成され、非リーフ ページ Pp1 内のそのポインターは新しい差分ページに設定されます。 さらに、特別なマージ差分ページ (緑色の三角形) が作成され、差分ページを示すようにリンクされます。 この段階では、両方のページ (差分ページとマージ差分ページ) は、同時のトランザクションには表示されません。 1 つのアトミック手順では、ページ マッピング テーブルのリーフレベル ページ P1 へのポインターはマージ差分ページを示すように更新されます。 この手順の後、10 のキー値 Pp1 のエントリはマージ差分ページを示すようになります。

手順 2: 非リーフ ページ 7 のキー値 Pp1 を表す行を削除し、キー値 10 のエントリが P1 を示すように更新する必要があります。 この処理を実行するために、新しい非リーフ ページ Pp2 が割り当てられ、キー値 Pp1 を表す行を除き、7 のすべての行がコピーされます。キー値 10 の行はページ P1 を示すように更新されます。 この処理が完了すると、1 つのアトミック手順で、Pp1 を示すページ マッピング テーブルのエントリは Pp2 を示すように更新されます。 Pp1 には到達できなくなります。

手順 3: リーフレベル ページ P2P1 はマージされ、差分ページは削除されます。 この処理を実行するために、新しいページ P3 が割り当てられ、P2P1 の行がマージされ、差分ページの変更は新しい P3 に含まれます。 次に、1 つのアトミック手順で、ページ P1 を示すページ マッピング テーブルのエントリは、ページ P3 を示すように更新されます。

メモリ最適化非クラスター化インデックスのパフォーマンスに関する考慮事項

非クラスター化インデックスのパフォーマンスは、非等値述語を使用してメモリ最適化テーブルにクエリを実行する場合のハッシュ インデックスよりも優れています。

メモリ最適化テーブルの列は、ハッシュ インデックスと非クラスター化インデックスの両方に含めることができます。

非クラスター化インデックス キーの列に、多数の重複値がある場合は、更新、挿入、および削除に関してパフォーマンスが低下します。 このような状況でパフォーマンスを向上させる 1 つの方法は、インデックス キーの選択度が高い列を追加することです。

インデックス メタデータ

インデックス定義、プロパティ、データ統計などのインデックス メタデータを調べるには、次のシステム ビューを使用します。

前のビューは、すべてのインデックスの種類に適用されます。 列ストア インデックスの場合は、さらに次のビューを使用します。

列ストア インデックスの場合、すべての列は付加列としてメタデータに格納されます。 列ストア インデックスはキー列を持ちません。

メモリ最適化テーブルのインデックスの場合は、さらに次のビューを使用します。