【実務・中級編】 マージ結合のチューニング – PostgreSQL

「また『Nested Loop』が暴走してるな……」

現場でパフォーマンスチューニングをしていると、そんな呟きが聞こえてくることがよくありますよね。特にデータ量が増えてくると、いつものクエリが急に重くなる。その犯人の多くは、不適切な結合戦略や、無駄なソート処理です。

今日は、PostgreSQLの「マージ結合(Merge Join)」について深掘りしようと思います。教科書には載っていない、現場で効く「インデックスを活用したソート回避」のテクニックを伝授しますね。

—

マージ結合って、結局何者?

マージ結合は、一言で言えば「両方のテーブルが結合キーでソートされている状態なら、前から順番に突き合わせるだけで終わる」という非常に効率的な結合方式です。

例えば、`users`テーブルと`orders`テーブルを`user_id`で結合するとしましょう。もし両方のデータが`user_id`順に並んでいれば、PostgreSQLは2つのテーブルを先頭からペラペラとめくるだけで結合を完了できる。これ、計算量で言うとO(N + M)で済むんです。

ところが、もしデータがソートされていなかったら? PostgreSQLはわざわざメモリ(`work_mem`)やディスクを使ってソート処理(Sortノード)を挟まなければなりません。これが、重いクエリの正体です。

—

「ソート済み」をインデックスでハックする

ここでエンジニアの腕の見せ所です。クエリが走るたびにデータベースに「ソートしてくれ」と頼むのではなく、「最初から並んでいる状態」をインデックスで作り出しておくのです。

例えば、こんな状況を想像してください。

— ユーザーIDで注文を検索する頻出クエリ
SELECT u.name, o.order_date
FROM users u
INNER JOIN orders o ON u.id = o.user_id
WHERE u.status = ‘active’
ORDER BY u.id, o.user_id;

もし`orders`テーブルの`user_id`にインデックスが張られていれば、PostgreSQLはそれを利用して「ソート済み」の状態を確保しようとします。

具体的なチューニングのヒント

1. カバリングインデックスを意識する
結合キーだけでなく、`SELECT`句で使うカラムもインデックスに含めてみてください。インデックスだけで検索が完結すれば(Index Only Scan)、ヒープ領域へのランダムアクセスが減り、驚くほど速度が上がります。

2. `work_mem` の調整を過信しない
「マージ結合が遅いから`work_mem`を増やせばいい」というアドバイスをたまに見かけますが、それは対症療法です。メモリを増やすより、インデックスを貼ってSortノード自体を消し去る方が、よっぽど本質的でスケーラブルな解決策になります。

3. `EXPLAIN ANALYZE` で「Sort」の文字を探せ
実行計画を見て、結合ノードの直前に `Sort` が入っていたら、それがコストの大部分を占めているはずです。そこをインデックスで潰すのが、チューニングの第一歩です。

—

注意点:マージ結合がいつも正義とは限らない

ただ、一つだけ注意を。マージ結合は「データ量がある程度大きい時」には最強ですが、対象データが極端に少ない場合は、Nested Loopの方が速いこともあります。

PostgreSQLのオプティマイザは優秀なので、基本的には任せておけばいいのですが、統計情報が古くて間違った判断をすることもあります。そんな時は `ANALYZE` を実行して統計情報を最新にするか、それでもダメならインデックスの構成を見直す。このプロセスを繰り返すのが、プロの仕事というものです。

—

最後に:エンジニアとして楽しもう

クエリチューニングは、いわばデータベースとの対話です。実行計画(`EXPLAIN`)を見ながら、「お、ここでソートしてるな。じゃあインデックス足して楽をさせてやるか」と考えるのは、パズルみたいで面白いと思いませんか?

「とりあえずインデックスを貼る」のではなく、「なぜここでマージ結合が選ばれたのか」「どうすればソートを回避できるか」を常に意識してみてください。その視点を持つだけで、あなたの書くSQLの質は間違いなく一段階上のレベルへ向かいます。

何か詰まったら、いつでも `EXPLAIN (ANALYZE, BUFFERS)` を叩いてみてください。データベースは、必ず何らかのヒントを返してくれますよ。

さて、次はどのインデックスを見直しましょうか?

コメント

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