やあ。最近、本番環境のクエリが重いって相談が増えてきたから、今日は「ハッシュ結合(Hash Join)」について、現場で役立つ話をしようと思う。
PostgreSQLの実行計画(`EXPLAIN`)を見ていると、`Hash Join`という文字列をよく目にするはずだ。これ、教科書通りに言えば「ハッシュテーブルを作って突合する」手法なんだけど、実務では「なぜオプティマイザがこれを選んだのか」「なぜたまに遅くなるのか」を理解していないと、痛い目を見るんだよね。
よし、深掘りしていこう。
—
1. ハッシュ結合の「正体」をイメージしてみよう
Nested Loop(入れ子ループ)が「Aというリストを手に、Bのリストを上から順に突き合わせる」作業だとしたら、ハッシュ結合は「片方のテーブルをメモリ上に、高速検索可能な辞書として展開する」作業だ。
1. Buildフェーズ: 小さい方のテーブル(内側)をスキャンして、結合キーをハッシュ値に変換し、メモリ上にハッシュテーブルを作る。
2. Probeフェーズ: もう片方のテーブル(外側)をスキャンしながら、キーをハッシュ化して、メモリ上のテーブルを一発で引きに行く。
この「一発で引ける」というのが強みなんだ。だから、データ量が数万件を超えてくると、Nested Loopより圧倒的に速い。
2. 実践:こんな時にハッシュ結合が光る
例えば、ECサイトの注文履歴データ(巨大)と、商品マスター(そこそこ)を結合する場合を考えてみよう。
SELECT orders.id, products.name
FROM orders
JOIN products ON orders.product_id = products.id;
もし`products`テーブルがメモリに収まるサイズなら、PostgreSQLは迷わずハッシュ結合を選択する。
現場のTips:
ここで重要なのは「`work_mem`」の設定だ。もし`products`のハッシュテーブルがメモリ(`work_mem`)に入りきらなくなると、PostgreSQLはデータを一時的にディスクへ書き出す(これを「Spill to disk」と言う)。
こうなると、せっかくの高速なメモリ結合が台無しだ。ディスクI/Oが発生した瞬間にクエリは失速する。`EXPLAIN ANALYZE`の出力で「Batches: 2」とか「Disk: XkB」なんて表示が出ていたら、要注意のサインだよ。
3. なぜ「Nested Loop」よりも「Hash Join」が選ばれるのか
初心者のうちは「全部インデックス貼れば速くなるでしょ?」と思いがちだけど、そうじゃない。
- 結合するテーブルが巨大で、かつインデックスが効きにくい範囲検索やフルスキャンが必要な場合。
- 結合対象の行数が非常に多い場合。
こういう時は、わざわざインデックスを辿るNested Loopよりも、メモリ上で一気にガサッと突き合わせるハッシュ結合の方が、トータルのコスト(I/OとCPUの負荷)が低くなるんだ。オプティマイザは、そのコストを秒単位で計算してくれているわけだね。
4. チューニングの勘所
ハッシュ結合を意図通りに動かすために、僕がいつも気をつけているポイントを3つ伝えておくよ。
1. 統計情報を最新に保つ: `ANALYZE`は必須だ。オプティマイザが「どっちが小さいテーブルか」を間違えると、メモリオーバーフローを起こして悲惨なことになる。
2. `work_mem`を過信しない: 闇雲に増やせばいいわけじゃない。コネクションごとに割り当てられるから、増やしすぎるとメモリ不足でOSがOOM Killerを呼んでくる。セッション単位で調整する癖をつけよう。
3. 実行計画を疑え: `EXPLAIN ANALYZE`を見て、予想行数(rows)と実際の行数(actual rows)が大きく乖離していないか確認すること。乖離しているなら、統計情報の更新か、インデックスの設計見直しが必要だ。
最後に:ハッシュ結合は「魔法」じゃない
ハッシュ結合は強力な武器だけど、あくまでメモリの恩恵を受けているということを忘れないでほしい。
たまに「ハッシュ結合を強制したいから`SET enable_hashjoin = on`にする」なんて荒技を使う人もいるけど、基本はおすすめしない。PostgreSQLのオプティマイザは、君が考えるよりずっと賢い。まずは彼がなぜその判断をしたのか、その「理由」を統計情報やクエリの構造から読み解くのが、エンジニアとしての近道だよ。
もしクエリが遅いなと感じたら、まずは`EXPLAIN (ANALYZE, BUFFERS)`を叩いてみて。何がどこで詰まっているのか、データは正直に教えてくれるからね。
また何か詰まったら、いつでも聞きに来てよ。次はマージ結合(Merge Join)の話でもしようか。それでは、良いクエリライフを!
コメント