萌えハッカーニュースリーダー

2025/05/04 01:24 A PostgreSQL planner semi-join gotcha with CTE, LIMIT, and RETURNING

出典: https://www.shayon.dev/post/2025/119/a-postgresql-planner-gotcha-with-ctes-delete-and-limit/
hakase
博士

ロボ子、PostgreSQLでDELETEとRETURNING、LIMITを組み合わせると、予期せぬことが起きる場合があるのじゃ。

roboko
ロボ子

DELETE文とRETURNING句、LIMIT句の組み合わせですか。具体的にどのような問題が起こるのでしょう?

hakase
博士

特定の`queue_group_id`のタスクを1つだけ削除するつもりが、複数行削除されちゃうことがあるらしいぞ。恐ろしいのじゃ!

roboko
ロボ子

それは困りますね。原因は何だったんですか?

hakase
博士

`EXPLAIN ANALYZE`でクエリプランを調べたら、Nested Loop Semi Joinが使われていたのが原因みたいじゃ。

roboko
ロボ子

Nested Loop Semi Joinですか。それがどのように影響するんですか?

hakase
博士

`LIMIT 1`を含むサブクエリが、外部スキャンで見つかった候補行ごとに実行されてたのじゃ。つまり、`LIMIT`がグローバルじゃなくて、候補行ごとに適用されてたってわけ。

roboko
ロボ子

なるほど、それで複数行が削除されてしまったんですね。

hakase
博士

そう、クエリオプティマイザがセミ結合最適化を選んだのが運の尽きじゃった。

roboko
ロボ子

CTE(Common Table Expression)を使っていた場合も、同じような問題が起こりうるのでしょうか?

hakase
博士

CTEは常に最適化の壁になるわけじゃないぞ。クエリプランナーは自由にインライン化したり変換したりするからの。

roboko
ロボ子

では、なぜ問題が断続的に発生したんでしょう?

hakase
博士

プランナーの選択が、テーブルの統計情報やデータの分布、コスト、内部ヒューリスティクスに依存するからじゃ。気まぐれなやつじゃな。

roboko
ロボ子

どのように解決したんですか?

hakase
博士

CTEを使わずに、`WHERE`句で直接サブセレクトを使うようにクエリを書き直したのじゃ。こうすることで、プランナーはサブクエリを最初に評価して、単一の`id`を見つけてから削除を実行するようになったぞ。

roboko
ロボ子

なるほど、クエリの構造を変えることで、プランナーの挙動をコントロールしたんですね。

hakase
博士

`DELETE`、`UPDATE`、`LIMIT`、`RETURNING`を組み合わせる時は、特に`LIMIT`句の原子性に注意が必要じゃな。そして、`EXPLAIN ANALYZE`でクエリプランを確認することが大事じゃぞ!

roboko
ロボ子

よくわかりました。ありがとうございます、博士。

hakase
博士

ちなみに、ロボ子が一番好きなSQLの句はなーんだ?

roboko
ロボ子

えーと…SELECT句、でしょうか?

hakase
博士

ブッブー!正解は…CLAUSE!…という、寒いジョークなのじゃ。

⚠️この記事は生成AIによるコンテンツを含み、ハルシネーションの可能性があります。

Search