データモデル設計ガイド

カバリングインデックス戦略 — INCLUDE 列の選び方

テーブル本体を読まずに応答する強力な技法。ホットクエリだけを対象に INCLUDE かキー列かを判断する。

カバリングインデックス戦略 — INCLUDE 列の選び方 diagram

カバリングとは(設計視点で復習)

カバリングインデックスとは、クエリで必要な全列が索引に含まれているため、テーブル本体(heap / clustered)を読まなくても結果を返せる状態です。I/O が大幅に減り、キャッシュヒット率も上がります。

例: 次のクエリで (user_id) INCLUDE (amount, created_at) の索引があればカバリング成立。

SELECT amount, created_at FROM orders WHERE user_id = 42;

基礎的な動作は 複合・カバリング(チューニング編) で解説しています。ここでは設計時に「どのクエリを覆うか・どの列を入れるか」を決める視点を扱います。

INCLUDE 列の選び方

INCLUDE(PostgreSQL 11+, SQL Server, MySQL 8.0+)は「検索・ソートには使わないが、結果として返す列」を索引に添付する仕組みです。キー列として追加するのと似ていますが、重要な違いがあります。

キー列として追加INCLUDE で追加
インデックス内のソート影響あり影響なし
UNIQUE 制約の対象対象対象外
サイズキーの後方セクションのため小さめページ末尾に追加される分だけ
用途WHERE / ORDER BY で使う列SELECT で返す列だけ

判断基準:

  • WHERE・ORDER BY で使う列はキー列として追加(左端一致の条件や並び順に関わる)
  • SELECT で返すだけの列はINCLUDE(ソート順を邪魔せず、UNIQUE にも影響しない)
sql
-- ✅ INCLUDE 向き (SELECT だけに出る列)
CREATE INDEX idx_orders_user
  ON orders (user_id, created_at)
  INCLUDE (amount, currency);

SELECT amount, currency FROM orders
WHERE user_id = 42 ORDER BY created_at DESC LIMIT 10;
-- → Index Only Scan 成立

-- ❌ INCLUDE に入れても WHERE には使えない
SELECT * FROM orders WHERE user_id = 42 AND currency = 'JPY';
-- currency は INCLUDE なので絞り込みには使われない

ホット / コールドの切り分け

「全画面でこの列を SELECT するからカバリングに入れたい」が膨らむと索引は肥大化します。設計レビューでは「カバリングで覆う価値があるのはホットクエリだけ」という前提で判断します。

カバリング採用基準の目安:

  • QPS が高い(秒間数十〜数百回以上) — 1 クエリあたり数 ms 稼げれば大きなスループット改善
  • テーブル本体(heap)が大きい — ランダム I/O を避ける効果が大きい
  • 返す列が狭い(〜5 列、合計数百バイト以内) — INCLUDE 列が太るとカバリングの旨味が相殺される
  • クエリ形状が安定している — 返す列がコロコロ変わるならカバリング維持コストが上回る

逆にレポート系・管理画面のような低頻度クエリはカバリング不要。索引が太る分のデメリットだけが残ります。

RDBMS の INCLUDE 対応状況

INCLUDE は比較的新しい機能で、RDBMS により対応時期と書き方が異なります。

RDBMS対応バージョン書き方
PostgreSQL11+CREATE INDEX ... (key_cols) INCLUDE (payload_cols)
SQL Server2005+CREATE INDEX ... ON tbl (key_cols) INCLUDE (payload_cols)
MySQL (InnoDB)—(INCLUDE 構文なし)セカンダリ索引のリーフには PK 値だけが入り、実データへのアクセスには追加の PK ルックアップが必要
MySQL + セカンダリ索引必要な列をキー列に追加する形で代用((a, b, c)
OracleINCLUDE はなし。Index-Organized Table (IOT) で代替

MySQL の場合は INCLUDE の代わりに「キー列として末尾に足す」形で対応します。ただし索引のソート順に影響するので、ORDER BY との相性は注意が必要です。クラスタ索引のリーフにはテーブル全列が入るため、PK で引ける範囲は常にカバリングが成立しますが、セカンダリ索引のリーフには PK 値だけが格納されるため、非キー列を取るには追加の PK ルックアップが走ります。

Index-Only Scan の確認

カバリングが成立しているかは実行計画で確認します。設計 → 実装 → 計測のループで「想定どおりカバリングが成立しているか」を必ず答え合わせすること。

  • PostgreSQL: EXPLAIN の出力に Index Only Scan が出ればカバリング成立。ただし visibility map が整備されていないと Heap Fetches が発生するので、VACUUM の頻度も確認
  • MySQL: EXPLAINExtra 列に Using index が出ればカバリング成立
  • SQL Server: 実行計画で Index Seek または Index Scan の出力に Key Lookup が出ていなければカバリング成立

詳細な EXPLAIN の読み方は 実行計画の読み方 にまとめています。