I run the following SQL that never commits the transaction:
rollback;
begin;
create table testing_stuff (
id serial,
num integer NOT NULL unique deferrable initially immediate
);
insert into testing_stuff (num) values (2), (1);
-- no issues with deferrable even though it swaps values
update testing_stuff set num = id;
-- fails even though I have not comitted;
update testing_stuff set num = 2;
-- this would have fixed it
update testing_stuff set num = id;
If I remove deferrable initially immediate
then it fails on update testing_stuff set num = id;
because each row is checked immediately. So deferrable initially immediate
is definitely making something deferred.
My understanding is that since the transaction is not committed, no constraints should be checked at all here, based on the SET CONSTRAINTS documentation which says:
DEFERRED constraints are not checked until transaction commit.
If I remove the line update testing_stuff set num = 2;
then everything works.
Also, if I use deferrable initially deferred
then I get the deferred behavior I expect.
What am I missing about transaction boundaries and/or initially immediate
here? Does initially immediate
also apply to all future statements in the transaction? But then why would deferrable initially immediate
avoid errors on update testing_stuff set num = id;
?
2
Answers
There are two things at play here:
DEFERRABLE
is notDEFERRED
. TheINITIALLY IMMEDIATE
part means that the constraint is not checked at the end of the transaction, but right after each statement. You’d need to writeDEFERRABLE INITIALLY DEFERRED
to have PostgreSQL check the constraint at the end of the transaction.DEFERRABLE
constraints are implemented differently fromNOT DEFERRABLE
constraints (the default) in PostgreSQL.NOT DEFERRABLE
unique constraints are verified at the moment when a row gets inserted into the unique index, so when you swap the two values, changing the first row triggers the constraint violation.DEFERRABLE
unique constraints are implemented with a system trigger that is executed at the end of the statement, so you get no constraint violation with aDEFERRABLE
constraint, because the data are consistent after the statement has swapped the values.The default
NOT DEFERRABLE
behavior is not in line with the SQL standard’s definition, which demands that constraints are checked after the statement. PostgreSQL’s default behavior is the way it is for performance reasone; if you need strict standard compliance, use aDEFERRABLE
constraint.In case of constraint violation, the exception is raised
deferrable initially deferred
deferrable initially immediate
not defferable
.The option with
immediate
in the name is in fact the second most immediate in terms of constraint validation overall, first most immediate only among the twodeferrable
s, which I guess is where that syntax is from.All constraints are checked truly immediately, on the spot. The difference is that regular, non-deferrable ones raise the exception as soon as they spot the violation, while the deferrable ones only take note to later re-check that.
The constraint part of
create table
,alter table..disable trigger
andset constraints
doc entries aren’t very clear about that and it’s further made worse by the different meanings of immediate. 64.5. Index Uniqueness Checks is a bit more helpful (here, immediately really means immediately):The comments in the source are pretty helpful, too:
But only if you remember that the default is the truly immediate, mid-statement and that it’s referring to the scheduled re-check. The base check is still immediate.
demo at db<>fiddle