【実務・中級編】 B-treeインデックス – PostgreSQL

「とりあえずインデックス」で満足してない?PostgreSQLのB-treeを深掘りする

現場でコードを書いていて、「検索が遅いからインデックスを貼る」というのは、もはや呼吸をするのと同じくらい当たり前の作業だよね。でも、そのインデックスが「なぜ速いのか」、そして「どんな時に裏切られるのか」をちゃんと説明できるエンジニアは、実はそう多くない。

今日は、PostgreSQLの「標準装備」であり、最も頼りになる相棒、B-treeインデックスについて、現場の視点から少し突っ込んだ話をしようと思う。

—

B-treeって、結局どうなってるの?

よくある勘違いが、「インデックスは単なるソート済みのリストだ」と思っていること。あれは間違いじゃないけど、データ量が増えたときを想像してみて。数百万、数千万行あるテーブルで、端から順に見ていくのは効率が悪すぎるよね。

PostgreSQLのB-treeは、「平衡木(Balanced Tree)」という構造をしている。これが何をもたらすかというと、ルート(根)からリーフ(葉)まで、どのデータにアクセスしても「同じステップ数でたどり着ける」ということだ。

  • ルート(Root): 最初の入り口。
  • ブランチ(Branch): 分岐点。ここで「右に行くか左に行くか」を判断する。
  • リーフ(Leaf): 実際のデータへのポインタ(TID)が格納されている終着点。

このおかげで、検索対象がどれだけ膨大になっても、ログオーダー(対数時間)の計算量で目的のデータに到達できる。これが、DBが爆速で動く一番の理由だよ。

—

どんな時に「B-tree」を愛すべきか

B-treeは万能選手だ。以下の演算子を使うクエリなら、迷わずB-treeを信頼していい。

  • `<` , `<=` , `=` , `>=` , `>`
  • `BETWEEN`
  • `IN`
  • `IS NULL` / `IS NOT NULL`

逆に、「前方一致」以外のLIKE検索(`LIKE ‘%hoge’`など)や、特定の関数を通した検索(`WHERE lower(name) = ‘taro’`)なんかは、通常のB-treeインデックスでは歯が立たない。ここが、インデックスが効かないクエリの典型例だね。

—

実践:こんな時、どうする?

例えば、ユーザーの「登録日」と「ステータス」で頻繁に絞り込むようなテーブルを考えてみよう。

— よくあるクエリ
SELECT FROM users
WHERE created_at > ‘2023-01-01’
AND status = ‘active’;

ここで「`created_at`にインデックスを貼ればいいや」と安直に考えると、意外とパフォーマンスが伸びないことがある。PostgreSQLのオプティマイザは、複数のインデックスをマージ(Bitmap Index Scan)することもあるけど、基本は「複合インデックス」を貼るのが鉄則だ。

— 複合インデックスの作成
CREATE INDEX idx_users_status_created_at ON users (status, created_at);

ここで重要な「順番」の話

複合インデックスを作るときは、「等価比較(=)」を先に、その後に「範囲比較(>、<)」を置くのがセオリーだ。

なぜかって? B-treeの構造上、先に「statusがactiveなもの」をガサッと絞り込んでから、その中で「日付順」に並んでいるデータを探すほうが、探索効率が段違いにいいからだよ。

—

先輩から最後に一つだけアドバイス

インデックスは「魔法の杖」じゃない。貼れば貼るほど、`INSERT`や`UPDATE`のたびにインデックスの再構築コストが発生して、書き込み性能が落ちる。

1. まずは `EXPLAIN ANALYZE` を叩く癖をつけること。 実際にインデックスが使われているか、予想外のフルスキャンになっていないかを確認する。
2. 使われないインデックスは罪。 定期的に `pg_stat_user_indexes` を見て、スキャン回数が0に近いインデックスは思い切って削除する勇気も必要だよ。

PostgreSQLのアーキテクチャを理解すると、クエリを書くのが楽しくなるはずだ。「なんとなく」から「納得して」インデックスを貼れるようになると、君の書くコードの信頼性はグッと上がる。

何か具体的に「このクエリ、遅いんだけど…」っていう悩みがあったら、いつでも相談してくれ。一緒に実行計画を眺めようじゃないか。

コメント

タイトルとURLをコピーしました