アトミックトランザクションで一意の違反を回避する


15

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.

回答:


24

@Craigが提供したものに加えて(およびその一部を修正します):

効果的なのPostgres 9.4UNIQUEPRIMARY KEYおよびEXCLUDE制約すぐにチェックされ、各行の後に定義されたときNOT DEFERRABLE。これは他の種類とは異なりますNOT DEFERRABLE、各ステートメントの後にREFERENCESチェックさ制約(現在は(外部キー)のみ)とます。私たちは、SOに関する次の関連する質問の下でこれをすべて解決しました。

それはないため、十分なUNIQUE(またはPRIMARY KEYまたはEXCLUDE)制約があることをDEFERRABLEして、あなたの提示のコードにするために複数のステートメントの作業を。

そして、あなたはこの目的のために使用することできませんALTER TABLE ... ALTER CONSTRAINTドキュメントごと:

ALTER CONSTRAINT

この形式は、以前に作成された制約の属性を変更します。現在、外部キー制約のみを変更できます

大胆な強調鉱山。代わりに使用します:

ALTER TABLE t
   DROP CONSTRAINT category_name_key
 , ADD  CONSTRAINT category_name_key UNIQUE(name) DEFERRABLE;

制約をドロップして1つのステートメントに追加し直して、問題のある行に侵入する時間枠がないようにします。大きなテーブルの場合、削除と再作成にはコストがかかるため、基になる一意のインデックスを何らかの方法で保存するのは魅力的です。残念ながら、それは標準ツールでは不可能なようです(そのためのソリューションがある場合はお知らせください!):

制約を遅延可能にする単一のステートメントの場合:

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を使用したクエリも単一のステートメントです。

WITH x AS (
    UPDATE category SET name = 'phones' WHERE id = 1
    )
UPDATE category SET name = 'tablets' WHERE id = 2;

ただし複数のステートメントを含むコードの場合、(さらに)実際に制約を延期するか、または次のように定義する必要があります。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ドキュメントごと:

参照される列は、 は、参照されるテーブルの非遅延一意または主キー制約の

したがって、両方を同時に使用することはできません。


13

私が理解しているように、ここでの問題は、各ステートメントの後に制約がチェックされることですが、トランザクションの終わりにチェックする必要があるため、中間状態を無視して、前の状態を後の状態と比較します。

もしそうなら、それは遅延可能な制約で可能です。

に記載されている制約SET CONSTRAINTSDEFERRABLE制約を参照してくださいCREATE TABLE

遅延制約にはコストがかかることに注意してください-コミット時にチェックするためにシステムはそれらのリストを保持する必要があるため、膨大な変更を行うトランザクションには適していません。また、チェックが遅くなります。

だから、おそらくあなたがしたいと思う:

ALTER TABLE mytable ALTER CONSTRAINT category_name_key DEFERRABLE;

ALTER TABLE制約の設定に制限があるように見えることに注意してくださいDEFERRABLE。代わりに制約DROPを再作成ADDする必要があります。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.