Database constraints are usually quite impatient. As soon as a statement violates a unique key, foreign key or another constraint, the database rejects it.
Most of the time this is exactly what we want. But sometimes an intermediate state is invalid even though the final state of the transaction is perfectly valid.
Consider a table that stores the order of items:
CREATE TABLE tasks ( id INTEGER PRIMARY KEY, position INTEGER NOT NULL, CONSTRAINT tasks_position_unique UNIQUE (position));
Suppose it contains:
id | position---+--------- 1 | 1 2 | 2
Now we want to swap the two positions. The obvious approach does not work:
UPDATE tasks SET position = 2 WHERE id = 1;UPDATE tasks SET position = 1 WHERE id = 2;
The first statement already violates the unique constraint because task 2 still has position 2. We could work around this by assigning a temporary value:
UPDATE tasks SET position = -1 WHERE id = 1;UPDATE tasks SET position = 1 WHERE id = 2;UPDATE tasks SET position = 2 WHERE id = 1;
But this is really an implementation detail leaking into our data manipulation. What we actually want to express is much simpler:
The positions have to be unique when the transaction is finished. We do not care about temporary duplicates while changing them.
This is what deferrable constraints are for.
Deferring a constraint
Both PostgreSQL and Oracle support the SQL keywords DEFERRABLE, INITIALLY IMMEDIATE and INITIALLY DEFERRED. A deferrable constraint can be switched from immediate checking to checking at the end of the transaction.
We can define our constraint like this:
CREATE TABLE tasks ( id INTEGER PRIMARY KEY, position INTEGER NOT NULL, CONSTRAINT tasks_position_unique UNIQUE (position) DEFERRABLE INITIALLY IMMEDIATE);
INITIALLY IMMEDIATE means that it behaves like an ordinary constraint by default. The important difference is that a transaction is allowed to defer it explicitly.
Before performing our swap we can write:
SET CONSTRAINTS tasks_position_unique DEFERRED;UPDATE tasks SET position = 2 WHERE id = 1;UPDATE tasks SET position = 1 WHERE id = 2;COMMIT;
After the first UPDATE the table temporarily contains the position 2 twice. This is allowed because the constraint has been deferred. After the second UPDATE all positions are unique again. When the transaction is committed, the constraint succeeds.
If we forgot the second update, the COMMIT would fail instead. So the constraint has not been disabled. Its check has merely been postponed.
Initially immediate or initially deferred?
A deferrable constraint has two useful default modes.
DEFERRABLE INITIALLY IMMEDIATE
means that the database normally checks the constraint immediately, but individual transactions can defer it.
DEFERRABLE INITIALLY DEFERRED
means that the database normally waits until the transaction is committed.
For most application tables INITIALLY IMMEDIATE is probably easier to reason about. Constraint violations still occur close to the statement that caused them, and deferral is explicitly enabled only for operations that need it.
You can also defer all deferrable constraints in a transaction:
SET CONSTRAINTS ALL DEFERRED;
Both PostgreSQL and Oracle support this form.
PostgreSQL and Oracle
The basic mechanism looks remarkably similar in PostgreSQL and Oracle. In both databases, constraints are NOT DEFERRABLE by default. If you want to change their checking mode during a transaction, they have to be created as DEFERRABLE. There are some differences in the details, though.
PostgreSQL currently allows deferral for UNIQUE, PRIMARY KEY, foreign key and EXCLUDE constraints. CHECK and NOT NULL constraints are always checked immediately.
Oracle’s model is somewhat broader. Oracle also supports deferrable constraints such as NOT NULL constraints. Its documentation explicitly describes a deferrable NOT NULL constraint whose violation is detected when the transaction is committed.
Another use case: Foreign keys
Unique constraints are an easy way to demonstrate the problem, but foreign keys are probably the more familiar use case.
Imagine importing a set of objects that reference each other. The final object graph is consistent, but the input format does not guarantee an insertion order that satisfies all foreign keys along the way. Without deferral, the application has to determine the correct ordering itself.
With deferrable foreign keys, all objects can be inserted first and referential integrity can be checked once the transaction is complete. This is particularly useful for bulk imports, cyclic relationships and more complicated restructuring operations.
Deferring vs. Disabling
There is an important conceptual difference between deferring a constraint and disabling it. When a constraint is disabled, invalid data may remain in the database. When a constraint is deferred, invalid data may only exist as an intermediate state inside a transaction.
Before that transaction can successfully finish, the database rules must be satisfied again. That makes deferrable constraints a nice fit for operations where the individual steps temporarily break an invariant, while the operation as a whole preserves it.
