データモデル設計ガイド

インデックス設計のトレードオフと設計レビュー

索引は書き込み・容量・バッファ・メンテの 4 コストを払う。本数の目安と未使用監視、レビュー用チェックリスト。

インデックス設計のトレードオフと設計レビュー diagram

索引が払う 4 つのコスト

索引は「読み取りを速くする」代わりに複数のコストを払います。設計段階で 4 つを意識しておきましょう。

コスト影響軽減策
書き込みコストINSERT / UPDATE / DELETE ごとに索引を更新。索引数に比例本数を絞る、更新しない列を選ぶ
ディスク容量テーブル本体と同等以上のサイズになり得る部分インデックス、INCLUDE の絞り込み
バッファプール圧迫索引ページがキャッシュを食い、テーブル本体が追い出される未使用索引の削除、索引サイズの監視
メンテナンスREINDEX / CONCURRENTLY / オンライン DDL ツールの運用工数本数を絞る、分割して実施

読み取り側の恩恵より書き込み側の負担が大きい索引は、むしろ有害です。「貼る前に、この索引を使うクエリは何か」を必ず答えられる状態で設計に入れましょう。

本数の目安 — 1 テーブル 5〜7 本

目安として、1 テーブルあたりの索引は PK を含めて 5〜7 本程度が上限感覚です。ワークロードで変動しますが、これを超えると書き込み性能の劣化やバッファプール圧迫が顕在化しやすくなります。

  • read-heavy(集計・レポート系): やや多めでも可。ただし書き込み性能を定期計測
  • write-heavy(ログ・IoT・メッセージング): 索引は最小限に。3〜4 本で踏みとどまる
  • mixed(一般的な業務): 5〜7 本を上限とし、追加するたびに 1 本削れないか検討

既に 10 本以上ある場合は、次を確認します。

  • 先頭列が同じ複合索引と単列索引の重複((a, b) があれば (a) は不要)
  • 使われていない索引(pg_stat_user_indexes / sys.schema_unused_indexes
  • 同じクエリパターンに対する複数の近似索引(整理統合できないか)

詳細な重複検知と未使用削除の手順は インデックスの罠 に。

CREATE INDEX CONCURRENTLY / オンライン DDL

本番稼働中のテーブルに索引を追加するときは、テーブルをロックせずに作る手順が必須です。RDBMS ごとの手段を知っておくと、設計時の「あとから追加」判断が変わります。

RDBMS非ロック手段注意点
PostgreSQLCREATE INDEX CONCURRENTLY通常より遅く、失敗すると INVALID 状態で残る → 削除して再実行
MySQL (InnoDB)8.0+ のオンライン DDL(ALGORITHM=INPLACE, LOCK=NONEUNIQUE の場合は SHARE ロックが入る
MySQL (大規模)pt-online-schema-change / gh-ostトリガーによるコピーベース。ディスク容量 × 2 が必要
SQL ServerWITH (ONLINE = ON)Enterprise Edition のみ
OracleONLINE オプションEnterprise Edition 以上

いずれも通常の CREATE INDEX より時間とリソースを使います。「後から貼れるから設計時は適当で OK」ではなく、「後から貼るのも高コスト」という前提で初期 DDL を詰める方が結局安上がりです。

未使用インデックスの監視

「使われていないのに維持コストだけ払っている索引」は意外と多く、定期監査の対象です。各 RDBMS で未使用検出の手段があります。

-- PostgreSQL
SELECT schemaname, relname, indexrelname, idx_scan,
       pg_size_pretty(pg_relation_size(indexrelid)) AS size
FROM pg_stat_user_indexes
WHERE idx_scan = 0
  AND indexrelname NOT LIKE '%_pkey'
ORDER BY pg_relation_size(indexrelid) DESC;

-- MySQL 8.0+
SELECT object_schema, object_name, index_name
FROM sys.schema_unused_indexes;

判断の注意点:

  • 統計はサーバー再起動でリセットされる → 数週間以上の運用観察が必要
  • 月次の集計バッチが使う索引は、計測タイミングでゼロに見える → 業務サイクルを考慮
  • UNIQUE 制約用の索引は「使われていなくても」制約のために残す必要がある

本気で削除する前に、「非アクティブ化(RENAME して残す)」してしばらく様子を見るのが安全です。

設計レビュー用チェックリスト

新規テーブル / 既存テーブル拡張の設計レビューで、索引観点を漏らさないためのチェックリスト。

テーブル単位

  • [ ] 主キーは時間順に増えるか(BIGINT AUTO_INCREMENT / UUID v7)
  • [ ] 想定される典型クエリが 3 件以上書き出してあるか
  • [ ] 各クエリを支える索引が明示的に設計されているか
  • [ ] 合計索引本数は 5〜7 本以内か(PK 含む)
  • [ ] 先頭列が同じ複合と単列の重複がないか((a, b) vs (a)

列単位

  • [ ] 全 FK 列に索引があるか(PostgreSQL / SQL Server / Oracle は手動)
  • [ ] 業務キーの UNIQUE 制約があるか
  • [ ] 論理削除を使う場合、部分インデックス(WHERE deleted_at IS NULL)を検討したか
  • [ ] マルチテナントの場合、tenant_id を全索引の先頭に含めたか

クエリ単位

  • [ ] WHERE 句の列が索引の先頭列と一致しているか(左端一致)
  • [ ] ORDER BY + LIMIT が索引のソート順で完結するか
  • [ ] 深いページネーションはキーセット方式になっているか
  • [ ] ORDER BY に tie-breaker(PK)が入っているか

このチェックリストを PR / MR テンプレートに入れておくと、レビューの抜け漏れが減ります。

まとめ — 索引は設計の一部

このシリーズで扱った内容は、すべてテーブルを作る前・作る瞬間に決めるべき索引の形です。後から遅くなってから慌てて貼るのではなく、設計レビューの対象として最初に詰めることで、長期の保守コストを桁で減らせます。

既に動いているクエリの最適化や EXPLAIN の読み方は、姉妹シリーズの チューニング編 を併せて参照してください。