At what point is a DEFERRED / DEFERRABLE/ IMMEDIATE unique / primary key constraint enforced exactly?
I am asking in connection with this answer.
Tried with this testbed in PostgreSQL 9.1.2:
CREATE TABLE tbl (
id integer
, txt text
, CONSTRAINT t_pkey PRIMARY KEY (id) DEFERRABLE INITIALLY IMMEDIATE
);
INSERT INTO t VALUES
(1, 'one')
, (2, 'two')
;
1) UPDATE statement modifying multiple rows:
UPDATE tbl t
SET id = t_old.id
FROM tbl t_old
WHERE (t.id, t_old.id) IN ((1,2), (2,1));
The above UPDATE works, though I expected it to fail. The constraint is defined INITIALLY IMMEDIATE and no SET CONSTRAINTS was issued.
What am I missing?
2) Data modifying CTE
A data modifying CTE works the same. Though either fails with a NOT DEFERRED PK:
WITH upd1 AS (
UPDATE tbl
SET id = 1
WHERE id = 2
)
UPDATE tbl
SET id = 2
WHERE id = 1;
The sub-statements in
WITHare executed concurrently with each other
and with the main query. Therefore, when using data-modifying
statements inWITH, the order in which the specified updates
actually happen is unpredictable. All the statements are executed with
the same snapshot (see Chapter 13), so they cannot "see" each
others’ effects on the target tables.
3) Multiple UPDATE statements in one transaction
Without SET CONSTRAINTS, this fails with a UNIQUE violation – as expected:
BEGIN;
-- SET CONSTRAINTS t_pkey DEFERRED;
UPDATE tbl SET id = 2 WHERE txt = 'one';
UPDATE tbl SET id = 1 WHERE txt = 'two';
COMMIT;
I remember having raised an almost identical point when PG9 was in alpha state. Here was the answer from Tom Lane (high-profile PG core developer):
http://archives.postgresql.org/pgsql-general/2010-01/msg00221.php
In short: won’t fix.
Not to say that I agree with your suggestion that the current behavior is a bug. Look at it from the opposite angle: it’s the behavior of
NOT DEFERRABLEthat is incorrect.In fact, the constraint violation in this UPDATE should never happen in any case, since at the end of the UPDATE the constraint is satisfied. The state at the end of the command is what matters. The intermediate states during the execution of a single statement should not be exposed to the user.
It seems like the PostgreSQL implements the non deferrable constraint by checking for duplicates after every row updated and failing immediately upon the first duplicate, which is essentially flawed. But this is a known problem, probably as old as PostgreSQL.
Nowadays the workaround for this is precisely to use a DEFERRABLE constraint. And there is some irony in that you’re looking at it as deficient because it fails to fail, while somehow it’s supposed to be the solution to the failure in the first place!
Summary of the status quo since PostgreSQL 9.1
NOT DEFERRABLEUNIQUEorPRIMARY KEYconstraints are checked after each row.DEFERRABLEconstraints set toIMMEDIATE(INITIALLY IMMEDIATEor viaSET CONSTRAINTS) are checked after each statement.DEFERRABLEconstraints set toDEFERRED(INITIALLY DEFERREDor viaSET CONSTRAINTS) are checked after each transaction.Note the special treatment of
UNIQUE/PRIMARY KEYconstraints.Quoting the manual page for
CREATE TABLE:While it states further down in the Compatibility section under
Non-deferred uniqueness constraints:Bold emphasis mine.
If you need any
FOREIGN KEYconstraints to reference the column(s),DEFERRABLEis not an option because (per documentation):