约束定义DEFERRABLE INTITIALLY IMMEDIATE仍然是DEFERRED?

时间:2012-04-05 16:19:29

标签: postgresql constraints postgresql-9.1

关于this answer,我偶然发现了一个无法解释的现象。

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

考虑以下演示。测试平台:

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));

目前的实施似乎有一个错误?上面的UPDATE虽然不应该,但仍有效。约束定义为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;

我引用manual on CTEs

  

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;

2 个答案:

答案 0 :(得分:30)

我记得当PG9处于阿尔法状态时已经提出了几乎相同的观点。以下是Tom Lane(备受瞩目的PG核心开发人员)的回答: http://archives.postgresql.org/pgsql-general/2010-01/msg00221.php

总之:不会修复。

不是说我同意你的建议,即目前的行为是一个错误。从相反的角度看待它:NOT DEFERRABLE的行为是不正确的。

实际上,在任何情况下都不应该发生此UPDATE中的约束违规,因为在UPDATE结束时满足约束。命令结束时的状态才是最重要的。执行单个语句期间的中间状态不应向用户公开。

似乎PostgreSQL通过在每一行更新后检查重复项来实现非延迟约束,并在第一次重复时立即失败,这实质上是有缺陷的。但这是一个已知问题,可能与PostgreSQL一样古老。 现在,解决方法恰好是使用DEFERRABLE约束。而且有一些讽刺意味着你认为它不足以因为它不会失败,而不知何故它应该首先解决失败的问题!


PostgreSQL 9.1

中的现状摘要
  • NOT DEFERRABLE UNIQUEPRIMARY KEY约束在每行后检查

  • 设置为DEFERRABLEIMMEDIATE或通过INITIALLY IMMEDIATE)的
  • SET CONSTRAINTS约束在每个声明后检查

  • 设置为DEFERRABLEDEFERRED或通过INITIALLY DEFERRED)的
  • SET CONSTRAINTS约束在每笔交易后检查

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

  

在每个命令之后,会立即检查不可延迟的约束。

虽然它在Non-deferred uniqueness constraints下的兼容性部分进一步说明:

  

UNIQUEPRIMARY KEY约束无法推迟时,PostgreSQL   无论何时插入行,立即检查唯一性   改性。 SQL标准说应该强制执行唯一性   仅在声明的最后;这对于   例如,单个命令更新多个键值。获得   符合标准的行为,将约束声明为DEFERRABLE但是   不推迟(即INITIALLY IMMEDIATE)。请注意,这可以   明显慢于直接唯一性检查。

大胆强调我的。

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

  

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

答案 1 :(得分:4)

这里可能存在轻微的文档错误,但不适用于您正在展示的情况。如果您开始一个事务并且一次尝试更新,它们就会失败,但是如果单个语句使事情处于良好状态,它就不会抱怨。文档说:

  

如果约束是可延迟的,则此子句指定默认时间   检查约束。如果约束是INITIALLY IMMEDIATE,那么   在每个陈述后检查。这是默认值。如果   约束是最初的延迟,只在结束时检查   事务。

这正是正在发生的事情。鉴于DEFERRABLE的文档,部分说明了

,给我带来了什么惊喜
  

将立即检查不可延迟的约束   每一个命令。

如果没有DEFERRABLE INITIALLY IMMEDIATE选项,示例更新将失败,即使UPDATE语句(可能构成“命令”)使事物处于良好状态。也许应该修改文档,以便在声明每行被修改时强制执行NOT DEFERRABLE约束