データベースインデックス入門 - B-treeの仕組みと、効くクエリ・効かないクエリ

データベースインデックス入門 - B-treeの仕組みと、効くクエリ・効かないクエリ

作成日:
読了:10
更新日:

「テーブルが大きくなったら急に遅くなった」——その多くはインデックスの不足や誤りが原因です。インデックスは魔法ではなく、仕組みを知れば「効く/効かない」が予測できるようになります。この記事では、PostgreSQL・MySQL 公式Use The Index, Luke を一次ソースに、B-tree インデックスの基礎を整理します。

インデックスとは

インデックスは、テーブル本体とは別に持つ「探索を速くするデータ構造」です。本の巻末索引を思い出してください。全ページをめくる(=フルスキャン)代わりに、索引から目的のページへ直行します。

  • フルスキャン(Seq Scan): 全行を順に読む。行数 N に比例(O(N))
  • インデックススキャン: 索引をたどって必要な行だけ取得(おおよそ O(log N))

B-tree はなぜ速いのか

既定のインデックスは B-tree(B+tree)です(PostgreSQL・MySQL とも CREATE INDEX で型を省略するとこれ)。

  • ソートされた木構造(ルート → ブランチ → リーフ)。各ノードに数百のエントリが入るため、100万件でも木の深さは10程度
  • リーフ同士は双方向リンクで連結され、順序を保持する(物理配置がバラバラでも順序走査できる)

この「ソート済みの木」という性質から、等価・範囲・前方一致・並び替えに強い、という効き方が決まります。

いつ効くか

B-tree インデックスが効く代表例(PostgreSQL 公式より)。

  • 等価・比較: = / < / <= / > / >= / BETWEEN / IN
  • 前方一致 LIKE: LIKE 'foo%'(先頭が固定の場合)
  • ORDER BY / GROUP BY: インデックスが既にソート済みなので並べ替えを省ける
  • JOIN のキー列

ただし、選択性(selectivity)が高いほど効くのが原則。「絞り込んだ後に少数の行しか残らない」列ほど価値があります。逆に性別フラグのような低選択性の列は効きにくい(絞っても大量に残るため、オプティマイザはフルスキャンを選ぶ)。

複合インデックスと「左端プレフィックス」

複数列のインデックスは列の順序が決定的です。(last_name, first_name, birth_date) の場合:

左端プレフィックスの法則
CREATE INDEX idx_user ON users (last_name, first_name, birth_date);
 
-- 効く(左端から連続して使う)
WHERE last_name = 'Yamada';
WHERE last_name = 'Yamada' AND first_name = 'Taro';
WHERE last_name = 'Yamada' AND first_name > 'S';   -- 等値 + 範囲
 
-- 効きにくい(左端 last_name を飛ばしている)
WHERE first_name = 'Taro';
WHERE birth_date = '1990-01-01';

左端の列から連続して条件に使われて初めて効きます。よく使う条件の列を左に置くのが設計の勘所です。

NOTE

近年のオプティマイザには「スキップスキャン」(PostgreSQL 18 など)があり、左端を飛ばした条件でも一部最適化される場合があります。ただし発動条件はバージョン・統計依存なので、基本は「左端プレフィックスで設計し、EXPLAIN で確認」が安全です。

カバリングインデックス(Index-Only Scan)

クエリが参照する列がすべてインデックスに含まれていれば、テーブル本体(ヒープ)を見ずに済みます(index-only scan)。PostgreSQL では INCLUDE で「検索キーにはしないが返す列」を足せます。

INCLUDE でカバリング
CREATE INDEX tab_x_y ON tab (x) INCLUDE (y);
-- これがヒープを読まずに済む
SELECT y FROM tab WHERE x = 'key';

効かない / 使われないケース

条件効くか理由
col = 'v'(等値)効くB-tree の等値検索
col > 100 AND col < 200(範囲)効く連続スキャン
col LIKE 'abc%'(前方一致)効く先頭で絞れる
col LIKE '%abc'(前方ワイルドカード)効かない木の先頭で絞れない
UPPER(col) = 'ABC'(列に関数)効かない列が関数で包まれ索引と結べない
varchar_col = 123(型不一致)効かない暗黙変換が列側に適用される
低選択性の列(性別など)効きにくい絞っても大量に残る
小さなテーブル効かないフルスキャンの方が安いと判断される

WARNING

いちばん多い失敗が「列に関数・演算をかける」です。WHERE UPPER(col) = 'ABC'WHERE col + 1 = 10 はインデックスが効きません。値側を変換する(WHERE col = 'abc')か、式インデックスCREATE INDEX ON t (UPPER(col)))を使います。型不一致(varchar 列に数値リテラル)も同じ罠です。

インデックスのコスト

「とりあえず全列にインデックス」は逆効果です。

  • 書き込みが重くなる: INSERT / UPDATE / DELETE のたびにすべてのインデックスを更新する(ノード分割も発生)
  • ストレージを消費する
  • 大量データ投入時は、一旦インデックスを落として投入後に再作成すると速いことがある

使われないインデックスは負債。読み取りの利益と書き込みの負担のバランスで設計します。

クラスタ化インデックスの違い(MySQL / PostgreSQL)

  • MySQL(InnoDB): 主キーがクラスタ化インデックスで、リーフに行データ本体を持つ。セカンダリインデックスは主キー値を格納するため、主キーは短い連番AUTO_INCREMENT 等)が有利
  • PostgreSQL: テーブルはヒープで、すべてのインデックスはセカンダリ(別領域)。通常は「インデックス → ヒープ」の2段。index-only scan はVACUUM 状態に依存する

EXPLAIN で確かめる

推測せず、EXPLAIN(実測は EXPLAIN ANALYZEで実行計画を見ます。Seq ScanIndex Scanを確認するのが第一歩です。

PostgreSQL の例
-- 大量ヒット → Seq Scan(インデックス不利)
EXPLAIN SELECT * FROM orders WHERE status = 'shipped';
--  Seq Scan on orders  (cost=0.00..1500.00 rows=9500 width=120)
 
-- 高選択性 → Index Scan
EXPLAIN SELECT * FROM orders WHERE order_id = 12345;
--  Index Scan using orders_pkey on orders  (cost=0.29..8.31 rows=1 width=120)

(cost=起動..総コスト rows=推定行数 ...) を読み、想定どおりインデックスが使われているかを確認します。

まとめ

  • インデックスは「探索を速くするソート済みの木(B-tree)」。等価・範囲・前方一致・ORDER BY・JOIN に効く
  • 選択性が高い列ほど価値がある。低選択性・小テーブルでは使われない
  • 複合インデックスは左端プレフィックス。よく使う列を左に
  • 列に関数・前方ワイルドカード・型不一致はインデックスを殺す
  • 書き込みコストとストレージとのトレードオフ。使われないインデックスは消す
  • InnoDB は主キー=クラスタ化、PostgreSQL はヒープ+セカンダリ。最後は EXPLAIN で確認

インデックスは「貼れば速くなる」ものではなく、クエリの形に合わせて設計するものです。仕組みと EXPLAIN を押さえれば、「なぜ遅いのか」を自分で説明できるようになります。

参考リンク

ページネーション設計 - オフセット方式とカーソル(キーセット)方式の使い分け

ページネーション設計 - オフセット方式とカーソル(キーセット)方式の使い分け

8

一覧の分割表示(ページネーション)を実務目線で整理します。LIMIT/OFFSET によるオフセット方式の利点(任意ページへジャンプ・総件数)と欠点(大きな OFFSET で遅い・挿入や削除でページずれ)、前回の最後の行を基準にするカーソル/キーセット方式の利点(大規模でも高速・安定)と欠点(任意ジャンプ不可・総ページ数が出しにくい)、複合キーのタイブレーク、不透明カーソルと API レスポンス設計(next_cursor / has_more / GraphQL Relay)、そして使い分けと落とし穴まで、PostgreSQL 公式・Use The Index, Luke・Stripe・Slack を一次ソースにまとめます。

Supabase 完全入門 - PostgreSQL ベースの BaaS と Firebase との徹底比較(2026年最新版)

Supabase 完全入門 - PostgreSQL ベースの BaaS と Firebase との徹底比較(2026年最新版)

23

オープンソースのFirebase代替として急成長を続けるSupabaseを徹底解説。PostgreSQL/Auth/Storage/Realtime/Edge Functions の主要機能、Next.js での実装サンプル、Row Level Securityの仕組み、2026年新機能(pg-deltaの宣言的スキーマ管理、PostgREST自動リトライ)から、Firebaseとの料金・アーキテクチャ・選定基準の違いまで、現場で必要な情報を網羅します。