「インデックスはあるのに遅い」を撲滅する!PostgreSQLのINCLUDE句活用術
現場でSQLのチューニングをしていると、必ず一度はぶつかる壁がある。「インデックスを貼ったはずなのに、なぜか期待した速度が出ない」という現象だ。
その原因の多くは、データベースがインデックスを検索に使った後、わざわざ元のテーブル(ヒープ)までデータを取りに行く「ヒープ参照」が発生していることにある。これを解決する強力な武器が、PostgreSQL 11から導入された「INCLUDE句」だ。
今日は、この「カバリングインデックス」を実務でどう使いこなすか、少し現場の知恵を交えて解説しようと思う。
—
なぜ「インデックスだけ」では不十分なのか?
まずは、よくあるインデックスの仕組みを思い出してほしい。
通常のB-treeインデックスは、「検索条件」に使う列をキーとして保持している。例えば `CREATE INDEX idx_user_email ON users(email);` というインデックスがあれば、メールアドレスからレコードの場所(ポインタ)を特定できる。
しかし、もしクエリが `SELECT email, display_name FROM users WHERE email = ‘xxx@example.com’;` だったらどうなるか?
1. `idx_user_email` でメールアドレスを検索し、レコードの場所を見つける。
2. その場所(ヒープ)まで移動して、`display_name` の値を取得する。
この「2. ヒープへの移動」が曲者なんだ。特にテーブルのデータ量が数百万件を超えてくると、このランダムアクセスがボトルネックになってクエリが重くなる。
そこで登場するのが「INCLUDE句」
「じゃあ、`display_name` もインデックスに含めればいいじゃないか」と思うかもしれない。確かに `CREATE INDEX idx_user_email_name ON users(email, display_name);` とすれば解決する。
だが、これには大きな落とし穴がある。B-treeインデックスのキーに含めると、その列はソート順の対象になってしまうんだ。もし `display_name` が巨大な文字列型だったり、頻繁に更新される列だったりすると、インデックスの肥大化やメンテナンスコストが跳ね上がる。
そこで、PostgreSQLの `INCLUDE` を使う。
CREATE INDEX idx_user_email_covering
ON users(email)
INCLUDE (display_name);
こう書くと、インデックスの「キー」には `email` だけが使われる。そして、`display_name` はインデックスの「おまけ(付加データ)」として内部に保存されるんだ。
- 検索: `email` を使って高速に行える。
- 取得: `display_name` がインデックス内に存在するため、ヒープまで見に行く必要がない(=Index Only Scanが成立!)。
これぞ、まさに「いいとこ取り」だと思わないか?
—
実務で「おっ、使えるな」と思うケース
僕がこの手法を積極的に導入するのは、主に以下のようなケースだ。
- ログテーブルの集計: `created_at` で絞り込んで、特定のステータスフラグだけを取得する場合。
- APIのレスポンス高速化: ユーザーIDで検索して、表示用のニックネームやアイコン画像URLだけをサクッと返したい場合。
- 複合インデックスの代替: 既存のインデックスの末尾に、頻繁に取得する列を追加したいが、インデックスの順序を変えたくない場合。
気をつけるべきポイント
便利な `INCLUDE` だが、万能薬ではない。これだけは覚えておいてくれ。
1. インデックスのサイズは増える: 付加データとはいえ、物理的にデータがインデックス内にコピーされるわけだから、当然サイズは肥大化する。ディスク容量とトレードオフだ。
2. 更新コスト: `INCLUDE` している列が更新されるたびに、インデックスの更新も発生する。頻繁に更新される列をホイホイ入れると、書き込み処理が極端に遅くなるぞ。
最後に
「インデックスを貼る」というのは、ただ作ればいいというものじゃない。そのSQLがどうデータを読みに行こうとしているのか、`EXPLAIN ANALYZE` を叩いて、`Index Only Scan` が出ているかを一つひとつ確認する。この泥臭い作業の積み重ねが、サービスのパフォーマンスを支えるんだ。
もし君が担当しているクエリで「もう少しだけ速くしたい」というものがあれば、ぜひ `INCLUDE` を試してみてほしい。きっと、期待以上のレスポンスが返ってくるはずだ。
次は、`WHERE` 句の `partial index` と組み合わせた最強のチューニング術について話すのもいいかもしれないな。また現場で会おう。健闘を祈る!
コメント