判断: どのインデックスタイプか?
適切なインデックスタイプは、クエリのアクセスパターン (等価、範囲、テキスト/JSON 検索)、列のカーディナリティ、読み書きのバランスによって決まる。誤ったインデックスは領域を無駄にし、書き込みも遅くする。このツリーは、クエリの形状を起点に B-tree、Hash、Bitmap、GIN/GiST、部分インデックス、カバリングインデックスの選択肢を導き、複合インデックスでは列の順序が重要であることを想起させる。
クエリはこの列をどのように使用しているか?
アクセスパターンはインデックス選択の最も決定的な要素。列を '=' で照合するだけか、範囲/ソート (>, <, BETWEEN, ORDER BY) を行うか、それとも全文、JSON フィールド、幾何データ/配列を検索するか? ここから始め、ニーズが明確になるまでインデックスタイプを選ばないこと。
- 等価と範囲/ソートの混在 (=, >, <, BETWEEN, ORDER BY)
- 等価のみ (=)、範囲やソートなし
- 全文、JSON/JSONB フィールド、配列、または幾何検索
- クエリが常に同じ小さな行のサブセットを対象とする (例: WHERE status='active')
列のカーディナリティ (個別値の数) は?
範囲/ソートが必要な場合のデフォルトは B-tree。ただし、読み取り中心のデータウェアハウスで個別値が非常に少ない列 (低カーディナリティ: 性別、ステータス、yes/no) には Bitmap インデックスのほうが適している場合がある。列には一意な値が多いか、それとも少数の値が繰り返されているか?
- 高カーディナリティ (一意な値が多い) または OLTP ワークロード
- 低カーディナリティかつ読み取り中心 / データウェアハウス (DWH)
クエリはインデックス内の列のみを読み取るか?
アクセスパターンは B-tree に適している。最後の最適化: クエリが返す列とフィルタする列がすべてインデックスに含まれていれば、エンジンはテーブルにアクセスせずインデックスのみで応答できる (index-only / カバリングスキャン)。クエリは少数の列を読み取るのか、それとも行の大部分が必要か?
- はい、少数の列で、テーブルにアクセスせずに応答できる
- いいえ、標準的なアクセスで十分
推奨: B-tree (デフォルト)
B-tree は、等価、範囲、ソート (ORDER BY)、LIKE 'abc%' による前方一致、MIN/MAX など、幅広いアクセスパターンをサポートする平衡木構造。ほとんどのリレーショナルエンジンのデフォルトのインデックスタイプであり、迷ったときの適切な出発点。
推奨: Hash インデックス (等価のみ)
Hash インデックスは列の値のハッシュを格納し、完全一致 ('=') の検索に限り非常に高速。範囲、ソート、前方一致の検索はサポートしないため、用途は限られる。
推奨: Bitmap インデックス (低カーディナリティ / DWH)
Bitmap インデックスは個別値ごとにビット配列を保持し、複数の低カーディナリティ条件をビット単位の AND/OR で非常に高速に結合する。データウェアハウスやレポートなど、読み取り中心のワークロードに強い。
推奨: GIN / GiST (テキスト、JSON、配列、幾何)
PostgreSQL には、標準の B-tree では扱えないアクセスタイプ向けの専用インデックスファミリーがある。GIN は値内の多数の構成要素 (単語、JSONB キー、配列要素) を指し、GiST は幾何/範囲/最近傍などのクエリに適している。
推奨: 部分 / フィルタ選択インデックス
部分インデックスは、テーブル全体ではなく条件 (WHERE ...) を満たす行のみをインデックス化する。クエリが常に同じ小さなサブセットを対象とする場合、インデックスははるかに小さく高速になり、書き込みコストも下がる。
推奨: カバリング (INCLUDE) インデックス
カバリングインデックスはクエリが必要とするすべての列を保持し、エンジンがテーブル (ヒープ) にまったくアクセスせずにインデックスのみで応答できるようにする (index-only scan)。少数の列を返す頻繁に実行されるクエリで大幅な高速化が得られる。