避免原子交易中的唯一违规

Pet*_*ryl 17 postgresql transaction unique-constraint

可以在 PostgreSQL 中创建原子事务吗?

考虑我有这些行的表类别:

id|name
--|---------
1 |'tablets'
2 |'phones'
Run Code Online (Sandbox Code Playgroud)

并且列名具有唯一约束。

如果我尝试:

BEGIN;
update "category" set name = 'phones' where id = 1;
update "category" set name = 'tablets' where id = 2;
COMMIT;
Run Code Online (Sandbox Code Playgroud)

我越来越:

ERROR:  duplicate key value violates unique constraint "category_name_key"
DETAIL:  Key (name)=(tablets) already exists.
Run Code Online (Sandbox Code Playgroud)

Erw*_*ter 25

除了@Craig 提供的内容(并纠正了其中的一些内容):

有效的Postgres 9.4UNIQUEPRIMARY KEYEXCLUDE约束立即检查各行后定义的时候NOT DEFERRABLE。这与在每个语句之后检查的其他类型的NOT DEFERRABLE约束(当前仅REFERENCES(外键))不同。我们在关于 SO 的这个相关问题下解决了所有这些问题:

这是不是足以让一个UNIQUE(或PRIMARY KEYEXCLUDE)约束是DEFERRABLE使你的代码提交多条语句的工作。

你也可以使用ALTER TABLE ... ALTER CONSTRAINT用于这一目的。根据文档:

ALTER CONSTRAINT

这种形式改变了先前创建的约束的属性。目前只能更改外键约束

大胆强调我的。改用:

ALTER TABLE t
   DROP CONSTRAINT category_name_key
 , ADD  CONSTRAINT category_name_key UNIQUE(name) DEFERRABLE;
Run Code Online (Sandbox Code Playgroud)

单个语句中删除并添加约束,这样任何人都没有时间窗口偷偷插入违规行。对于大表,以某种方式保存底层唯一索引很诱人,因为删除和重新创建它的成本很高。唉,这似乎无法使用标准工具(如果您有解决方案,请告诉我们!):

对于使约束可延迟的单个语句就足够了:

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;
Run Code Online (Sandbox Code Playgroud)

与CTE的查询也是一个单一的语句:

WITH x AS (
    UPDATE category SET name = 'phones' WHERE id = 1
    )
UPDATE category SET name = 'tablets' WHERE id = 2;
Run Code Online (Sandbox Code Playgroud)

但是,对于包含多个语句的代码,您(另外)需要实际推迟约束 - 或者将其定义为“INITIALLY DEFERRED任一”通常比上述更昂贵。但是将所有内容打包成一个语句可能并不容易。

BEGIN;
SET CONSTRAINTS category_name_key DEFERRED;
UPDATE category SET name = 'phones'  WHERE id = 1;
UPDATE category SET name = 'tablets' WHERE id = 2;
COMMIT;
Run Code Online (Sandbox Code Playgroud)

要知道一个的限制与连接FOREIGN KEY的限制,虽然。根据文档:

被引用的列必须是被引用表中不可延迟的唯一或主键约束的列。

所以你不能同时拥有两者。


Cra*_*ger 13

据我了解,您的问题是在每个语句之后检查约束,但您希望在事务结束时检查它,因此它将前状态与后状态进行比较,忽略中间状态。

如果是这样,则可以使用延迟约束

请参阅SET CONSTRAINTS和 中记录的DEFERRABLE约束CREATE TABLE

请注意,延迟约束是有成本的——系统必须保留一个它们的列表以在提交时检查,因此它们不适用于进行大量更改的事务。它们的检查速度也较慢。

所以我想你可能想要:

ALTER TABLE mytable ALTER CONSTRAINT category_name_key DEFERRABLE;
Run Code Online (Sandbox Code Playgroud)

请注意,ALTER TABLE将约束设置为DEFERRABLE; 您可能不得不改为DROP重新ADD约束。