14

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

版本:
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 DEFERREDpk:

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

2 回答 2

31

我记得当 PG9 处于 alpha 状态时,我提出了一个几乎相同的点。这是 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 UNIQUE在每一行之后PRIMARY KEY检查约束。

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

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

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

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

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

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

大胆强调我的。

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

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

于 2012-04-05T19:55:46.093 回答
6

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

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

这正是似乎正在发生的事情。考虑到 的文档,让我感到惊讶的是DEFERRABLE,其中部分内容是:

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

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

于 2012-04-05T17:32:35.977 回答