【テクニカル・上級編】 結合の除去 – PostgreSQL

PostgreSQLがこっそり裏でやっている「結合の除去(Join Removal)」の美学

PostgreSQLのオプティマイザは、長年かけて驚くほど洗練されてきました。特に、クエリプランナーが実行計画を立てる際、ユーザーが書いたSQLを「そのまま忠実に実行する」のではなく、「結果を変えずに効率的な形へ変形する」能力は、PostgreSQLの大きな武器の一つです。

今日は、その中でも少し玄人好みな最適化手法である「結合の除去(Join Removal)」について深掘りしてみましょう。

—

なぜ「結合」を捨てるのか?

僕たちがパフォーマンスチューニングをしているとき、不要な結合を削ることは基本中の基本ですよね。でも、時には「アプリケーションの汎用的なビュー」や「ORMが生成したクエリ」など、どうしても冗長な結合が紛れ込んでしまうことがあります。

例えば、こんなケースを想像してみてください。

SELECT orders.id, orders.total
FROM orders
LEFT JOIN users ON orders.user_id = users.id;

もし、`orders.user_id` に外部キー制約(Foreign Key)が貼られていて、`users` テーブルの `id` が主キー(Primary Key)である場合。PostgreSQLは、この `LEFT JOIN` が「結果セットの行数を増やさない」ことを、メタデータから推論します。

結果として、`users` テーブルを参照しなくても結果が変わらないと判断されれば、オプティマイザは物理的な結合処理(Nested LoopなりHash Joinなり)を完全にスキップします。これが「結合の除去」です。

オプティマイザが「不要」と判断する条件

ここがエンジニアとして面白いところです。PostgreSQLがこの最適化を適用するには、いくつかの厳格な条件が必要です。

1. 参照の不在: 結合先のテーブルの列が、`SELECT` 句や `WHERE` 句、`ORDER BY` 句などで一切参照されていないこと。
2. 一意性の保証: 結合条件が、結合先のテーブルにおいて「一意」であることが保証されていること(Unique IndexやPrimary Keyの存在)。
3. NULLの扱い: `LEFT JOIN` であったとしても、結合先の結果がすべてNULLになるような「行の増減」が発生しないことが論理的に証明できること。

特に2番目の「一意性の保証」が肝です。PostgreSQLは `pg_constraint` や `pg_index` を見て、結合先のテーブルでキーが一意であることを確認します。もし、ここに統計情報や制約が正しく設定されていないと、オプティマイザは安全側に倒して「結合せざるを得ない」と判断します。

パフォーマンストラブルシューティングの落とし穴

「結合の除去」を意識するようになると、プランナの挙動が今まで以上に予測できるようになります。もし、明らかに不要なはずのテーブルがプランに出てきたら、まず疑うべきは「制約の欠落」です。

  • 外部キー制約の欠落: 物理的にはリレーションがあっても、DDLで `FOREIGN KEY` を定義していない場合、PostgreSQLは結合の除去に必要な論理的推論を放棄することがあります。
  • NULL許容列の罠: 結合条件となる列が `NOT NULL` 制約を持たない場合、結合の結果が意図せず変化する可能性があるため、プランナは慎重になります。

実務の現場では、「なんとなく結合したまま放置されているレガシーなビュー」に出くわすことがよくあります。そうしたビューを掘り起こし、制約を整理するだけで、クエリの実行計画から不要なHash Joinが消え、スループットが劇的に改善する――そんな瞬間こそ、エンジニアとしての醍醐味ですよね。

最後に:オプティマイザを信じる、しかし過信しない

PostgreSQLのオプティマイザは非常に優秀ですが、あくまで「メタデータが正しく整備されていること」を前提としています。

クエリが遅いと感じたとき、単にインデックスを貼るだけでなく、「そもそもこの結合は、SQLの文法上は必要でも、論理的には不要ではないか?」という視点を持ってみてください。

`EXPLAIN` を眺めながら、プランナが不要な結合を削ぎ落としてくれたときの快感。これを知ると、データベースを触るのがもっと楽しくなるはずです。

皆さんのDBで、不要な結合が眠っていないか、一度 `EXPLAIN` してみることをお勧めします。案外、驚くような「無駄」が見つかるかもしれませんよ。

コメント

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