约束定义了DEFERRABLE INITIALLY IMMEDIATE仍然是DEFERRED?

关于这个答案,我偶然发现了一个我无法解释的现象。

版:
在x86_64-unknown-linux-gnu上的PostgreSQL 9.1.2,由gcc-4.4.real编译(Debian 4.4.5-8)4.4.5,64位

考虑下面的演示。 testing平台:

CREATE TEMP TABLE t ( id integer ,txt text ,CONSTRAINT t_pkey PRIMARY KEY (id) DEFERRABLE INITIALLY IMMEDIATE ); INSERT INTO t VALUES (1, 'one') ,(2, 'two'); 

1)修改多行的UPDATE语句:

 UPDATE t SET id = t_old.id FROM t t_old WHERE (t.id, t_old.id) IN ((1,2), (2,1)); 

在目前的实施中似乎有一个错误? 上面的更新工作,虽然它不应该 。 约束被定义为INITIALLY IMMEDIATE ,我没有使用SET CONSTRAINTS

我错过了什么或者这是一个(相当无害)的错误?


2)数据修改CTE

因此,一个数据修改CTE也能工作,虽然它失败了一个NOT DEFERRED PK:

 WITH x AS ( UPDATE t SET id = 1 WHERE id = 2 ) UPDATE t SET id = 2 WHERE id = 1; 

我引用CTE手册 :

WITH中的子语句与主查询并行执行。 因此,在WITH中使用数据修改语句时,指定的更新实际发生的顺序是不可预知的。 所有的语句都使用相同的快照执行(见第13章),所以他们不能在目标表上“看到”彼此的影响。


3)在一个事务中有多个UPDATE语句

没有SET CONSTRAINTS ,这将失败与UNIQUE违规 – 正如所料:

 BEGIN; -- SET CONSTRAINTS t_pkey DEFERRED; UPDATE t SET id = 2 WHERE txt = 'one'; UPDATE t SET id = 1 WHERE txt = 'two'; COMMIT; 

我记得当PG9处于alpha状态时,提出了几乎相同的观点。 这里是Tom Lane(PG的核心开发人员)的答案: http : //archives.postgresql.org/pgsql-general/2010-01/msg00221.php

总之:不会修复。

不是说我同意你的build议,即当前的行为是一个错误。 从相反的angular度来看:这是NOT DEFERRABLE的行为是不正确的。

实际上,这个UPDATE中的约束违规不应该发生在任何情况下,因为UPDATE的约束满足了。 指挥结束时的状态是重要的。 执行单个语句期间的中间状态不应该暴露给用户。

看起来PostgreSQL通过在每一行更新之后检查重复项并在第一次重复时立即失败来实现不可延迟的约束,这在本质上是有缺陷的。 但这是一个已知的问题,可能和PostgreSQL一样古老。 现在解决这个问题的方法恰恰是使用了一个DEFERRABLE约束。 而有些讽刺的是,你认为这是不足之处,因为它没有失败,不知怎的,它应该是解决失败的办法!


PostgreSQL 9.1中的现状总结

  • NOT DEFERRABLE UNIQUEPRIMARY KEY约束在每行之后被检查。

  • 在每个语句之后检查设置为IMMEDIATEINITIALLY IMMEDIATE或通过SET CONSTRAINTS )的DEFERRABLE约束。

  • 在每个事务之后检查设置为DEFERREDINITIALLY DEFERRED或通过SET CONSTRAINTS )的DEFERRABLE约束。

请注意UNIQUE / PRIMARY KEY约束的特殊处理。 引用CREATE TABLE的手册页:

每个命令后都会立即检查一个不可推迟的约束。

虽然它在“ Non-deferred uniqueness constraints下的“ 兼容性”部分中进一步阐述:

UNIQUEPRIMARY KEY约束不可延迟时,PostgreSQL会在插入或修改行时立即检查唯一性 。 SQL标准说, 只有在声明结束时才应该强制执行唯一性。 例如,当一个命令更新多个键值时,这会有所不同。 要获得符合标准的行为,请将约束声明为DEFERRABLE但不要延迟(即INITIALLY IMMEDIATE )。 请注意,这可能比直接唯一性检查慢得多。

大胆重视我的。

如果您需要任何FOREIGN KEY约束来引用列,则DEFERRABLE不是一个选项,因为( 按文档 ):

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

这里可能会有一些文档错误,但是不会显示出来。 如果你开始一个交易,并尝试更新一次,他们失败了,但如果一个单一的声明使事情处于良好的状态,它不会抱怨。 文档说:

如果约束是可延迟的,则此子句指定检查约束的缺省时间。 如果约束是INITIALLY IMMEDIATE,则在每个语句之后进行检查。 这是默认的。 如果约束是INITIALLY DEFERRED,只在事务结束时检查。

这似乎是正在发生的事情。 令我感到惊讶的是,由于DEFERRABLE的文档部分说到:

每个命令后都会立即检查一个不可推迟的约束。

如果没有DEFERRABLE INITIALLY IMMEDIATE选项,即使UPDATE语句(大概构成“命令”)使事情处于良好状态,示例更新也失败。 也许文档应该被修改,说一个NOT DEFERRABLE约束被强制执行, 因为每一行都被语句修改了?