【実務・中級編】 カバリングインデックス (INCLUDE句) – PostgreSQL

「インデックスはあるのに遅い」を撲滅する!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` と組み合わせた最強のチューニング術について話すのもいいかもしれないな。また現場で会おう。健闘を祈る!

コメント

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