web-dev-qa-db-ja.com

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

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.
15
Petr Přikryl

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

有効なPostgres 9.4UNIQUEPRIMARY 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;

Howevermultiple 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に注意してください。 ドキュメントごと:

参照される列は、参照されるテーブルの遅延不可の一意または主キー制約の列である必要があります。

したがって、両方を同時に持つことはできません。

24

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

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

SET CONSTRAINTS に記載されている CREATE TABLE およびDEFERRABLE制約を参照してください。

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

したがって、おそらく次のことを行いたいと思います。

ALTER TABLE mytable ALTER CONSTRAINT category_name_key DEFERRABLE;

DEFERRABLEへのALTER TABLE設定の制約には制限があるように見えることに注意してください。代わりに、制約をDROPおよびre _ADDする必要がある場合があります。

13
Craig Ringer