SQL ServerのIncluded Columns(INCLUDE)とは?複合インデックスとの違い・利点・使い分けを具体例で解説

SQL Server

SQL Serverで非クラスター化インデックスを設計していると、通常の複合インデックスにする方法と、INCLUDE句を使ってIncluded Columns(付加列・非キー列)を追加する方法のどちらを選ぶべきか迷うことがあります。

Included Columnsの大きな特徴は、検索や並べ替えに必要な列だけをインデックスキーにし、SELECTで取得するだけの列は非キー列としてインデックスのリーフレベルに保持できることです。これにより、キーを必要以上に太くせずにカバリングインデックスを作れることが、通常の複合インデックスと比べた主な利点です。

この記事では、SQL ServerのIncluded Columnsと複合インデックスの構造上の違い、Index SeekやKey Lookupとの関係、どの列をキーにしてどの列をINCLUDEへ入れるべきかを具体的なSQLを使って解説します。

SQL ServerのIncluded Columnsとは

Included Columnsとは、非クラスター化インデックスに追加できる非キー列です。T-SQLではCREATE INDEXINCLUDE句を使って指定します。

たとえば、顧客番号で検索し、その顧客の氏名とメールアドレスを取得するクエリがあるとします。

SELECT CustomerName, Email
FROM Customers
WHERE CustomerId = 1001;

このクエリ向けのインデックスは、次のように作成できます。

CREATE NONCLUSTERED INDEX IX_Customers_CustomerId
ON Customers (CustomerId)
INCLUDE (CustomerName, Email);

この場合、CustomerIdがインデックスキーで、CustomerNameEmailがIncluded Columnsです。Microsoftも、Included Columnsを利用すると、検索や参照に使用する列をキーに保ちながら、クエリをカバーするための列を非キー列として追加できると説明しています。[参照] Microsoft Learn「包含列を含むインデックスを作成する」

通常の複合インデックスとの最大の違いは「キーになるかどうか」

同じ3列を通常の複合インデックスとして作るなら、次のような定義も可能です。

CREATE NONCLUSTERED INDEX IX_Customers_Composite
ON Customers (CustomerId, CustomerName, Email);

こちらでは3列すべてがインデックスキーです。一方、INCLUDEを使った場合はCustomerIdだけがキーで、残り2列はリーフレベルに格納される非キー列になります。

比較項目 複合インデックス INCLUDEを使うインデックス
CustomerId キー キー
CustomerName キー Included Column
Email キー Included Column
検索順序を構成する すべてのキー列が関係 キー列のみ
クエリをカバーできる 可能 可能

つまり、「同じ列がインデックスに入っている」という点だけを見ると似ていますが、内部で担当する役割が違います。

利点1:インデックスキーを小さく保ったままクエリをカバーできる

Included Columnsの代表的なメリットは、インデックスキーそのものを必要以上に大きくせず、SELECTに必要な列をインデックス内へ保持できることです。

たとえば次のクエリを考えます。

SELECT OrderDate, CustomerName, TotalAmount
FROM Orders
WHERE CustomerId = 1001;

検索条件として使っているのはCustomerIdです。OrderDateCustomerNameTotalAmountは結果として取得したいだけで、CustomerIdを探すためのキーである必要がないとします。

その場合は、次のような設計が候補になります。

CREATE NONCLUSTERED INDEX IX_Orders_CustomerId
ON Orders (CustomerId)
INCLUDE (OrderDate, CustomerName, TotalAmount);

Microsoftのインデックス設計ガイドでも、検索条件、集計、並べ替えなどに必要な列をキー列とし、クエリをカバーするために取得するその他の列をIncluded Columnsにする設計が推奨されています。[参照] Microsoft Learn「SQL Server Index Architecture and Design Guide」

利点2:Key Lookupを回避できる場合がある

Included Columnsが性能改善に役立つ代表的なケースが、Key Lookupの回避です。

たとえばCustomerIdだけの非クラスター化インデックスが存在するとします。

CREATE INDEX IX_Orders_CustomerId
ON Orders (CustomerId);

この状態で次のSQLを実行します。

SELECT CustomerId, OrderDate, TotalAmount
FROM Orders
WHERE CustomerId = 1001;

SQL ServerはCustomerIdをインデックスから効率よく探せても、OrderDateTotalAmountがその非クラスター化インデックスにない場合、必要なデータを取得するためにベーステーブルやクラスター化インデックスへ追加アクセスする実行計画になることがあります。

そこで次のようにします。

CREATE INDEX IX_Orders_CustomerId
ON Orders (CustomerId)
INCLUDE (OrderDate, TotalAmount);

必要な列がすべてインデックス内に存在すれば、そのインデックスだけでクエリを処理できる可能性があります。このようなインデックスを一般にカバリングインデックスと呼びます。

Microsoftも、クエリで必要となる列がキー列または非キー列としてすべてインデックスに含まれていれば、テーブルやクラスター化インデックス本体へのアクセスを避けられ、ディスクI/Oを減らせる可能性があると説明しています。

利点3:SELECTするだけの列をキーにしなくてよい

通常の複合インデックスでは、定義したすべての列がインデックスキーになります。しかしSELECT結果として返したいだけの列まで、必ずキーにする必要があるわけではありません。

たとえば次のSQLでは、検索に使うのはStatusOrderDateです。

SELECT OrderId, CustomerName, TotalAmount
FROM Orders
WHERE Status = 'OPEN'
  AND OrderDate >= '2026-08-01';

この場合、設計候補は次のようになります。

CREATE INDEX IX_Orders_Status_OrderDate
ON Orders (Status, OrderDate)
INCLUDE (OrderId, CustomerName, TotalAmount);

StatusOrderDateは検索条件に利用するためキーにし、OrderIdCustomerNameTotalAmountは結果取得用としてINCLUDEへ入れています。

このように、「探すための列」と「取り出すためだけの列」を区別できることがIncluded Columnsを理解する重要なポイントです。

Included Columnsはインデックスのリーフレベルに格納される

SQL Serverの非クラスター化インデックスはB+ツリー構造を持ちます。インデックスキーはツリーをたどって目的の行を探すために使用されます。

Included Columnsはキーではなく、非クラスター化インデックスのリーフレベルに格納されます。そのため、ツリーの検索順序を構成するための列ではありません。

概念的には次のように考えられます。

非リーフレベル
CustomerId(キー)
    ↓
CustomerId(キー) + CustomerName(INCLUDE) + Email(INCLUDE)
リーフレベル

この構造のおかげで、SELECT時に必要な情報は保持しつつ、キーを検索目的に必要な列へ絞ることができます。

通常の複合インデックスより内部レベルのキーを細くできる

たとえば次の複合インデックスでは、3列すべてがキーです。

CREATE INDEX IX_A
ON Customers (CustomerId, CustomerName, Email);

一方、次の設計ならキーはCustomerIdだけです。

CREATE INDEX IX_B
ON Customers (CustomerId)
INCLUDE (CustomerName, Email);

どちらも特定のクエリをカバーできる可能性がありますが、後者ではCustomerNameとEmailが検索キーの一部になりません。そのため、非リーフレベルを含むインデックスキーをよりコンパクトに保てます。

ただし、INCLUDEに指定したデータが無料で格納されるわけではありません。Included Columnsの値はリーフレベルへ保存されるので、その分インデックスサイズは増加します。

利点4:キー列数やキーサイズの制約を受けにくい

SQL Serverではインデックスキーに列数やサイズの制限があります。Included Columnsはインデックスキーではないため、キー列数やキーサイズを計算するときには含まれません。

現行のSQL Serverでは、通常の非クラスター化インデックスキーについて最大32キー列、最大1,700バイトという制限があります。Included Columnsはこのキー列数・キーサイズの計算対象外です。

そのため、多数の出力列をカバーしたいからといってすべてを複合キーにするのではなく、検索に必要な列だけをキーにして、残りをINCLUDEへ配置できます。[参照] Microsoft Learn「包含列を含むインデックスを作成する」

なお、古いSQL Serverではキー列数・キーサイズの上限が異なるため、古いバージョンを運用している場合は対象バージョンの公式ドキュメントを確認するのが安全です。

利点5:キーとして利用できない一部のデータ型も含められる

Included Columnsには、インデックスのキー列としては利用できないデータ型の一部を指定できるという利点もあります。

現在のSQL Serverでは、textntextimageを除くデータ型を非キー列として利用できるとMicrosoftは説明しています。そのため、キーとして適さない比較的大きなデータをクエリカバーのために保持できるケースがあります。

ただし、varchar(max)nvarchar(max)varbinary(max)xmlなど大きくなり得る列をINCLUDEへ追加すると、インデックスの容量が大幅に増える可能性があります。使用できることと、追加したほうがよいことは別です。

Included ColumnsはWHERE句の検索キーにはならない

INCLUDEを理解するうえで非常に重要なのが、Included Columnはインデックスキーではないという点です。

たとえば次のインデックスがあるとします。

CREATE INDEX IX_Orders_CustomerId
ON Orders (CustomerId)
INCLUDE (Status);

このインデックスの検索キーはCustomerIdです。Statusはインデックス内には存在しますが、CustomerIdと同じ意味でB+ツリーをたどるためのキーにはなりません。

もし次のような検索を頻繁に高速化したいなら、Statusを単純にINCLUDEへ入れるだけでは目的に合わない可能性があります。

SELECT OrderId
FROM Orders
WHERE Status = 'OPEN';

Statusによる絞り込みそのものを効率化したいのであれば、Statusをキー列に含めるインデックスを検討します。

複合インデックスにするべき列とは

複合インデックスのキーに向いているのは、WHERE句、JOIN条件、ORDER BY、GROUP BYなど、検索・結合・並べ替え・集計のアクセスパターンに関わる列です。

たとえば次のSQLがあります。

SELECT CustomerName, TotalAmount
FROM Orders
WHERE CustomerId = 1001
ORDER BY OrderDate DESC;

このクエリなら、候補の一つは次のようになります。

CREATE INDEX IX_Orders_CustomerId_OrderDate
ON Orders (CustomerId, OrderDate DESC)
INCLUDE (CustomerName, TotalAmount);

CustomerIdは検索条件、OrderDateは並べ替えに関係します。一方、CustomerNameとTotalAmountは取得するだけなのでIncluded Columnsへ回しています。

ただし、最適なキー順序は実際のWHERE条件、データ分布、選択性、JOIN、ORDER BY、既存インデックスなどによって変わります。「WHERE列は必ず全部キー」「SELECT列は必ず全部INCLUDE」という機械的なルールではありません。

複合インデックスのキー順序には意味がある

通常の複合インデックスでは、キー列の順序が重要です。

たとえば次の2つは同じインデックスではありません。

CREATE INDEX IX_1
ON Orders (CustomerId, OrderDate);
CREATE INDEX IX_2
ON Orders (OrderDate, CustomerId);

先頭キーが異なるため、効率よく利用できる検索条件も変わります。一方、INCLUDEへ指定した非キー列については、Microsoftのドキュメント上、INCLUDEリスト内の列順序は、そのインデックスを利用するクエリの性能に影響しないとされています。

この点からも、キー列は「検索構造を作る列」、Included Columnsは「リーフに追加データを持たせる列」と考えると理解しやすくなります。

UNIQUEインデックスではIncluded Columnsは一意性判定に使われない

Included Columnsはキーではないため、UNIQUEインデックスの一意性判定にも含まれません。

たとえば次のインデックスを作成したとします。

CREATE UNIQUE INDEX UX_Users_Email
ON Users (Email)
INCLUDE (UserName, CreatedAt);

この場合、一意でなければならないのはEmailです。UserNameやCreatedAtの値は一意性の判定に影響しません。

これは、次の複合UNIQUEインデックスとは意味が異なります。

CREATE UNIQUE INDEX UX_Users
ON Users (Email, UserName, CreatedAt);

後者では3列の組み合わせが一意であればよいため、Email単独の重複を禁止するインデックスとは制約の意味が変わります。INCLUDEは単なる構文上の書き換えではなく、キーの意味そのものを分ける機能です。

Included Columnsにもデメリットがある

INCLUDEへ列を追加すればするほど高速になるわけではありません。Included Columnsはリーフレベルへ実際に格納されるため、列を増やすとインデックス自体が大きくなります。

インデックスが大きくなると、より多くのディスク容量やメモリを必要とし、1ページに格納できる行数も減ります。その結果、I/Oやキャッシュ効率に悪影響を与える場合があります。

さらに、INSERT、UPDATE、DELETEではインデックス側のデータも保守する必要があります。特にIncluded Columnの値をUPDATEすれば、その非クラスター化インデックスも更新対象になります。

Microsoftも、非常に幅の広い非クラスター化インデックスでは、読み取り性能の改善と、ストレージ容量・更新性能の悪化を比較する必要があると注意しています。[参照] Microsoft Learn「SQL Server Index Architecture and Design Guide」

具体例:複合キーにすべて入れる場合とINCLUDEを比較する

次のようなテーブルがあるとします。

Orders
----------------------
OrderId
CustomerId
OrderDate
Status
CustomerName
ShippingAddress
TotalAmount

主なクエリが次のSQLだとします。

SELECT OrderDate, CustomerName, TotalAmount
FROM Orders
WHERE CustomerId = @CustomerId;

すべてを複合キーにするなら、次のようになります。

CREATE INDEX IX_Orders_A
ON Orders
(CustomerId, OrderDate, CustomerName, TotalAmount);

これでもクエリをカバーできる可能性があります。しかし検索条件はCustomerIdだけであり、残り3列を検索キーとして利用する目的がなければ、次のほうが設計意図を明確にできます。

CREATE INDEX IX_Orders_B
ON Orders (CustomerId)
INCLUDE (OrderDate, CustomerName, TotalAmount);

後者なら「CustomerIdで探し、必要な出力値をリーフから取得する」というクエリの性質をそのままインデックス設計へ反映できます。

実行プランを確認して効果を判断する

インデックス設計は、SQLを見ただけで絶対的な正解を決めるものではありません。実際のデータ件数や分布、既存インデックス、統計情報によってSQL Serverのクエリオプティマイザーが選択する実行プランは変化します。

Included Columnsを追加する前後では、SSMSの実際の実行プランなどを使ってIndex SeekIndex ScanKey Lookupなどを確認します。

たとえば大量の行に対してKey Lookupが繰り返されているクエリで、取得列を適切にINCLUDEへ追加するとLookupがなくなり、論理読み取り数や処理時間が改善する場合があります。

逆に数件しか取得しないクエリなら、Key Lookupが存在していても十分高速で、幅の広いカバリングインデックスを追加するほうが全体として不利なこともあります。

「Key Lookupがある=悪い」ではない

実行プランでKey Lookupを見つけると、すぐにINCLUDEを追加したくなることがあります。しかしKey Lookupそのものは異常な処理ではありません。

検索結果が少数なら、細いインデックスで対象行を探し、必要な数行だけLookupするほうが、大量のIncluded Columnsを持つインデックスを常時維持するより合理的な場合があります。

重要なのは、Lookupの回数やコスト、対象SQLの実行頻度、読み取り量、テーブル更新頻度などを含めて判断することです。

Included Columnsと通常の複合インデックスの使い分け

実務では「その列を何のためにインデックスへ入れるのか」を考えると使い分けやすくなります。

列の用途 候補
WHEREで効率よく検索したい キー列
JOINキーとして利用する キー列を検討
ORDER BYをインデックス順で処理したい キー列を検討
GROUP BY・集計処理に利用する キー列を検討
SELECT結果として取得するだけ INCLUDEを検討
Key Lookup回避のため保持したい INCLUDEを検討

ただしこれは設計の出発点であり、実際にはクエリオプティマイザーの実行プランとワークロードを確認して決めます。

大量のINCLUDEを安易に追加しない

「SELECT句に書かれている列を全部INCLUDEへ入れればよい」と考えると、非常に幅の広いインデックスが増えてしまいます。

たとえば30列あるテーブルで、20列をINCLUDEしたインデックスを複数作れば、ベーステーブルと似た量のデータを複数の非クラスター化インデックスにも保持することになります。

読み取り中心の分析システムならメリットが上回る場合もありますが、頻繁にINSERTやUPDATEされる業務システムでは更新コストが問題になる可能性があります。

そのため、「このクエリはどの程度頻繁に実行されるか」「何行返すか」「Lookupが本当にボトルネックか」「追加する列は本当に必要か」を確認してから設計することが重要です。

まとめ:INCLUDEの利点はキーを細くしたままクエリをカバーできること

SQL ServerのIncluded Columnsを利用した非クラスター化インデックスの最大の利点は、検索・結合・並べ替えなどに必要な列だけをインデックスキーとし、取得に必要なその他の列を非キー列として保持できることです。

通常の複合インデックスでは定義した列がすべてキーになりますが、INCLUDEを利用すると出力専用の列までキーへ含める必要がありません。そのため、インデックスキーを比較的小さく保ちながらカバリングインデックスを作り、条件によってはベーステーブルやクラスター化インデックスへのKey Lookupを回避してI/Oを減らせます。

またIncluded Columnsはキー列数・キーサイズの計算対象にならず、キーとして利用できない一部のデータ型も格納できます。一方で、リーフレベルには実データが保存されるため、インデックス容量とINSERT・UPDATE・DELETEの維持コストは増加します。

したがって、WHERE・JOIN・ORDER BYなどで探索に必要な列はキー、SELECTで返すだけの列はINCLUDEを候補にするという考え方から始め、最終的には実行プラン、論理読み取り数、更新頻度、データ量を確認して判断するのがSQL Serverの実践的なインデックス設計です。

コメント

タイトルとURLをコピーしました