

PostgreSQLのSET CONSTRAINTS ALL DEFERREDを書いても外部キーチェックが即時のままだった
この記事は、構成と文章の整理にAIを使っています。
あくまで読みやすくするための補助で、内容そのものをAIにすべて任せて書いているわけではありませんが、ところどころ相談しながら整えています。
トランザクション内で外部キー制約のチェックを後回しにしたくて SET CONSTRAINTS ALL DEFERRED を使ったが、想定通りに動かなかったのでメモ。
やろうとしたこと
参照先・参照元のテーブルに対して、DELETEの順序を気にせず一括で消したかった。PostgreSQLには制約チェックを遅延させる仕組みがあるので、それを使えば順不同でDELETEしても最後にまとめてチェックしてくれるはず、という想定だった。
BEGIN;
SET CONSTRAINTS ALL DEFERRED;
DELETE FROM tablename;
SET CONSTRAINTS ALL IMMEDIATE;
COMMIT;起きたこと
SET CONSTRAINTS ALL DEFERRED; を実行しても、DELETEの時点で普通に外部キー制約違反のエラーが出た。DEFERREDにしたはずなのに即時チェックされている状態。
原因
SET CONSTRAINTS はあくまで「制約チェックのタイミングを切り替えるスイッチ」であって、対象の制約自体が DEFERRABLE で定義されていないと何も起きないらしい。PostgreSQLの外部キー制約はデフォルトで NOT DEFERRABLE INITIALLY IMMEDIATE になっているので、単純に ALTER TABLE ... ADD CONSTRAINT ... FOREIGN KEY ... しただけのテーブルは対象外になる。
効かない例:
ALTER TABLE child ADD CONSTRAINT fk_parent FOREIGN KEY (parent_id) REFERENCES parent(id);効く例:
ALTER TABLE child ADD CONSTRAINT fk_parent FOREIGN KEY (parent_id) REFERENCES parent(id) DEFERRABLE INITIALLY IMMEDIATE;DEFERRABLE INITIALLY IMMEDIATE にしておけば、普段は即時チェックのまま、必要なときだけ SET CONSTRAINTS ALL DEFERRED で遅延させられる。
補足
SET CONSTRAINTS ALL DEFERREDはトランザクション内でのみ有効。トランザクションが終わると自動的にリセットされるので、次のトランザクションに影響しない。SET CONSTRAINTS ALL IMMEDIATEを単体で実行すると、それまで保留していたチェックがその場で走る。DEFERRED中に一時的に整合性が崩れた状態でIMMEDIATEに戻すと、そこでエラーになる。- 上のSQL例、最後が
CMMIT;になってるとタイポで構文エラーになる。COMMIT;が正しい。地味にやりがちなので注意。
結論
SET CONSTRAINTS ALL DEFERRED を使いたいなら、対象の外部キー制約を先に DEFERRABLE にしておく必要がある。既存のテーブルで制約を後から DEFERRABLE に変えたい場合は、いったん制約をDROPして DEFERRABLE 付きで再作成することになる(実機での検証はまだしていない)。