カバリングインデックス戦略 — INCLUDE 列の選び方
テーブル本体を読まずに応答する強力な技法。ホットクエリだけを対象に INCLUDE かキー列かを判断する。
カバリングとは(設計視点で復習)
カバリングインデックスとは、クエリで必要な全列が索引に含まれているため、テーブル本体(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 にも影響しない)
-- ✅ 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 | 対応バージョン | 書き方 |
|---|---|---|
| PostgreSQL | 11+ | CREATE INDEX ... (key_cols) INCLUDE (payload_cols) |
| SQL Server | 2005+ | CREATE INDEX ... ON tbl (key_cols) INCLUDE (payload_cols) |
| MySQL (InnoDB) | —(INCLUDE 構文なし) | セカンダリ索引のリーフには PK 値だけが入り、実データへのアクセスには追加の PK ルックアップが必要 |
| MySQL + セカンダリ索引 | — | 必要な列をキー列に追加する形で代用((a, b, c)) |
| Oracle | — | INCLUDE はなし。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:
EXPLAINのExtra列にUsing indexが出ればカバリング成立 - SQL Server: 実行計画で
Index SeekまたはIndex Scanの出力にKey Lookupが出ていなければカバリング成立
詳細な EXPLAIN の読み方は 実行計画の読み方 にまとめています。
