PostgreSQLでアトミックトランザクションを作成することは可能ですか?
次の行を含むテーブルカテゴリがあるとします。
id|name
--|---------
1 |'tablets'
2 |'phones'
また、列名には一意の制約があります。
私が試した場合:
BEGIN;
update "category" set name = 'phones' where id = 1;
update "category" set name = 'tablets' where id = 2;
COMMIT;
私は得ています:
ERROR: duplicate key value violates unique constraint "category_name_key"
DETAIL: Key (name)=(tablets) already exists.
@ Craigが提供したもの に加えて(およびその一部を修正):
有効なPostgres 9.4、UNIQUE
、PRIMARY KEY
およびEXCLUDE
制約がすぐにチェックされます各行の後NOT DEFERRABLE
を定義した場合。これは、他の種類のNOT DEFERRABLE
制約(現在はREFERENCES
(外部キー)のみ)とは異なり、各ステートメントの後にチェックされます。私たちはSOに関するこの関連質問の下でこれをすべて解決しました:
UNIQUE
(またはPRIMARY KEY
またはEXCLUDE
)制約がDEFERRABLE
になるのに十分なnotmultipleステートメントで提示されたコードを機能させるため。
not使用できます この目的のために。 ドキュメントごと:ALTER TABLE ... ALTER CONSTRAINT
ALTER CONSTRAINT
このフォームは、以前に作成された制約の属性を変更します。現在変更できるのは外部キー制約のみです-。
大胆な強調鉱山。代わりに使用:
ALTER TABLE t
DROP CONSTRAINT category_name_key
, ADD CONSTRAINT category_name_key UNIQUE(name) DEFERRABLE;
single statementに制約をドロップして追加します。これにより、問題のある行に誰かが忍び込む時間枠がなくなります。大きなテーブルの場合、削除して再作成するとコストがかかるため、基になる一意のインデックスを何らかの方法で保存するのは魅力的です。悲しいかな、それは標準のツールでは可能ではないようです(そのための解決策がある場合は、お知らせください!):
単一ステートメントの場合、制約を延期可能にすることで十分です。
UPDATE category c
SET name = c_old.name
FROM category c_old
WHERE c.id IN (1,2)
AND c_old.id IN (1,2)
AND c.id <> c_old.id;
CTEを使用したクエリもsingleステートメントです。
WITH x AS (
UPDATE category SET name = 'phones' WHERE id = 1
)
UPDATE category SET name = 'tablets' WHERE id = 2;
However、multiple statementを使用したコードの場合、(さらに)実際に制約を延期するか、定義する必要がありますas INITIALLY DEFERRED
どちらも通常、上記よりも高価です。ただし、すべてを1つのステートメントにまとめるのは簡単ではありません。
BEGIN;
SET CONSTRAINTS category_name_key DEFERRED;
UPDATE category SET name = 'phones' WHERE id = 1;
UPDATE category SET name = 'tablets' WHERE id = 2;
COMMIT;
ただし、FOREIGN KEY
制約に関連してlimitationに注意してください。 ドキュメントごと:
参照される列は、参照されるテーブルの遅延不可の一意または主キー制約の列である必要があります。
したがって、両方を同時に持つことはできません。
私の理解では、ここでの問題は、各ステートメントの後に制約がチェックされることですが、トランザクションの最後にチェックしたいので、中間の状態を無視して、前の状態と後の状態を比較します。
もしそうなら、遅延可能な制約でそれは可能です。
SET CONSTRAINTS
に記載されている CREATE TABLE
およびDEFERRABLE
制約を参照してください。
遅延制約にはコストがかかることに注意してください。システムはコミット時にチェックするためにそれらのリストを保持する必要があるため、大きな変更のセットを行うトランザクションには適していません。また、チェックに時間がかかります。
したがって、おそらく次のことを行いたいと思います。
ALTER TABLE mytable ALTER CONSTRAINT category_name_key DEFERRABLE;
DEFERRABLE
へのALTER TABLE
設定の制約には制限があるように見えることに注意してください。代わりに、制約をDROP
およびre _ADD
する必要がある場合があります。