PostgreSQLの「守護神」B-treeインデックスを、現場の視点で理解する
こんにちは。現場でPostgreSQLと長く付き合っていると、新人のエンジニアから「とりあえずインデックスを貼っておけば速くなるんですよね?」と聞かれることがよくあります。
その答えは、「半分正解で、半分は危険な賭け」です。
PostgreSQLを使いこなす上で避けて通れないのがB-treeインデックス。今回は、教科書的な定義をなぞるだけじゃなく、実務で「なぜこれが最強のデフォルトなのか」、そして「どう付き合っていくべきか」という話をしようと思います。
—
そもそも、B-treeって何者なのか?
B-tree(Balanced Tree)は、PostgreSQLでインデックスを指定せずに作成するとデフォルトで選ばれる、言わば「標準装備」のアルゴリズムです。
イメージとしては、巨大な辞書を想像してください。辞書を最初から最後まで1ページずつめくって言葉を探すのは、データ量が増えれば増えるほど地獄ですよね。B-treeは、その辞書の「索引」を階層構造にして整理整頓したようなものです。
- 木構造のバランス: 常に左右の深さが揃うように制御されています。これが「B(Balanced)」の由来。どのデータを探すにも、ほぼ一定のステップ数でたどり着けるのが最大の強みです。
- ソート済み: データが常に昇順(あるいは降順)で並んでいるので、範囲検索がめちゃくちゃ速いんです。
こんな時に、B-treeは輝く!
B-treeが「万能選手」と呼ばれるのは、以下の演算子で威力を発揮するからです。
- `<` , `<=`
- `=`
- `>=` , `>`
- `BETWEEN`
- `IN`
- `IS NULL` / `IS NOT NULL`
逆に言えば、「=」や「範囲」で絞り込むクエリがメインなら、まずはB-treeを疑え、というのが鉄則です。
—
実践:インデックス作成のリアル
例えば、ユーザーのログイン履歴を記録するテーブルがあったとします。
CREATE TABLE login_history (
id serial PRIMARY KEY,
user_id int NOT NULL,
login_at timestamp NOT NULL
);
もし「特定のユーザーの直近1ヶ月のログを見たい」というクエリが頻発するなら、インデックスは必須です。
— ユーザーIDで絞り込み、時間でソートするようなケース
CREATE INDEX idx_user_login ON login_history (user_id, login_at);
ここでポイントなのが、カラムの順番です。B-treeは左側のカラムから順にソートされるので、`user_id`(等価比較)を先に書き、その後に`login_at`(範囲比較)を置くのが定石。もし逆にすると、インデックスの効き目がガクンと落ちることがあります。
注意点:インデックスは「タダ」じゃない
先輩として一つ釘を刺しておきたいのは、「インデックスは貼れば貼るほど書き込み性能が落ちる」という事実です。
データが1件追加されるたびに、PostgreSQLはテーブル本体だけでなく、関連するすべてのインデックスに対しても「木構造のどこに書き込むか」を計算し、書き込みを行います。
- 読み取り専用に近いテーブル: インデックスは多めでもOK。
- 頻繁にUPDATE/INSERTが発生するテーブル: インデックスは慎重に厳選する。
このバランス感覚こそが、ジュニアから脱却する第一歩です。
—
まとめ:現場で迷ったらどうするか
最後に、実務でインデックスを貼る時に僕がいつも自分に問いかけているチェックリストを置いておきます。
1. そのクエリ、本当にボトルネックになっているか?(EXPLAIN ANALYZEで実行計画を見る癖をつけてください)
2. インデックスが肥大化しすぎていないか?(使われないインデックスはただの負債です)
3. 複合インデックスの順番は適切か?(「一番絞り込みが強いカラム」を左に!)
B-treeは非常に優秀ですが、魔法の杖ではありません。まずは「どうやってデータが検索されているのか」を`EXPLAIN`コマンドで覗き見ることから始めてみてください。
もし「インデックスを貼っても速くならない!」と悩んだら、またいつでも相談してください。その時は別のインデックスタイプ(GINやBRINなど)の話もしましょう。
それでは、良いDBライフを!
コメント