避免原子事务中的唯一违规


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.

Answers:


24

除了@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;

将约束拖放到单个语句中,这样任何人都没有时间窗口可以潜入有问题的行中。对于大表,以某种方式保留底层唯一索引将很诱人,因为删除和重新创建它的成本很高。遗憾的是,使用标准工具似乎无法实现(如果您有解决方案,请告诉我们!):

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

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为通常比上述方法更昂贵。但是将所有内容打包成一个语句可能并不容易。

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约束。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.