skip to content

A batch job opens a transaction, issues SET CONSTRAINTS ALL DEFERRED, and then performs a few million writes. What operational consequences does that have — for error reporting, for concurrent sessions, and for the transaction's resource use?

level: seniorimportance: should knowfreq 28%

answer

  1. Transaction-scoped; only affects DEFERRABLE constraints
  2. Error at COMMIT — constraint name, no statement
  3. SET CONSTRAINTS ALL IMMEDIATE forces pending checks early
  4. One queued after-trigger event per row → memory + commit burst
  5. Long tx: locks held, vacuum horizon pinned

basics

~20 s

Violations now surface at COMMIT with a constraint name but no guilty statement, and the entire batch is lost. The engine queues one pending check per affected row, costing memory. Concurrent sessions see nothing invalid, but hold-open locks last longer.

solid answer

~60 s

Three separate consequences: **Error reporting.** Deferred checks fire at COMMIT. You learn the constraint name, sometimes the offending key, but not which of the millions of statements produced it. Diagnosis means re-running the batch with immediate checking or querying for orphans after the fact. Your application must also catch integrity errors on the commit path, which many frameworks do not expect. **Resource use.** Engines record a pending check per affected row — in PostgreSQL these are queued after-triggers. For a multi-million-row transaction that queue is large enough to matter, and it is evaluated in a burst at COMMIT, so commit latency spikes and the whole batch is a single all-or-nothing unit. **Concurrency.** Other sessions never observe the intermediate invalid state, since it is uncommitted — deferral weakens the intra-transaction invariant, not isolation. But a long transaction holds its row locks and pins cleanup (in PostgreSQL, it holds back the transaction horizon so dead tuples cannot be vacuumed) for its whole life. Also note `SET CONSTRAINTS ALL DEFERRED` silently affects only constraints declared DEFERRABLE — the rest keep checking immediately.

code

sql · 5 lines
sql
BEGIN;
SET CONSTRAINTS ALL DEFERRED;
-- ... bulk load ...
SET CONSTRAINTS ALL IMMEDIATE;  -- pending checks run here, transaction still open
COMMIT;

go deeper

for a junior

Know that the errors move to COMMIT and that the whole transaction is rolled back if any deferred check fails.

for a middle

Add that only DEFERRABLE constraints are affected, and that SET CONSTRAINTS ALL IMMEDIATE forces the pending checks to run early.

for a senior

Cover the pending-check queue and commit-latency burst, the loss of statement attribution on the commit path, and the long-transaction effects on locks and vacuum.

for a principal

Argue the batch architecture: chunked short transactions with immediate checks and parents-before-children ordering, reserving blanket deferral for genuinely atomic restores.

## What the statement actually does `SET CONSTRAINTS ALL DEFERRED` is transaction-scoped: it switches every constraint that was *declared* DEFERRABLE into commit-time checking for the remainder of this transaction. It does not touch NOT DEFERRABLE constraints — those keep firing per statement. That asymmetry is the first thing to say, because teams routinely add the line to a batch job, see no change in behaviour, and conclude deferral is broken when in fact none of the schema's constraints were deferrable in the first place. Naming a specific non-deferrable constraint in `SET CONSTRAINTS` raises an error, which is the more helpful form when you want to be sure. The symmetric form, `SET CONSTRAINTS ALL IMMEDIATE`, is more useful than it looks: it forces all currently-pending checks to be evaluated *right there*. Running it just before COMMIT converts a mystery commit-time failure into a failure at a known point in the script, and lets you keep the transaction open to inspect what went wrong. ## Error reporting: the real cost With immediate checking, an integrity error names the statement that caused it — you know the table, the row and the moment. With everything deferred, the error is raised by COMMIT after the last statement has long finished. The engine reports the constraint name and often the offending key value, but there is no statement attribution, no line number in your batch, and by the time the exception arrives the transaction is already aborting. Practical consequences for a batch job: - The whole batch is lost, not one statement. There is no "skip the bad row and continue", because you cannot catch the error mid-flight. - Your commit path must handle integrity exceptions. Application code and ORMs typically wrap statement execution in error handling and treat commit as a formality; a deferred violation appears where nobody is catching it. - Debugging usually means a second run with `SET CONSTRAINTS ALL IMMEDIATE`, or writing an anti-join query after the fact to find the orphan rows. For these reasons, defer *specific named constraints* for the specific statements that need it, rather than blanket-deferring everything for convenience. ## Resource use Deferred checks are not free bookkeeping. The engine must remember what to verify. In PostgreSQL, foreign-key and unique constraint checks are implemented as after-triggers, and deferring them means queuing one trigger event per affected row in the transaction's event queue. A transaction touching millions of constrained rows therefore accumulates millions of queued events; the queue lives in memory up to a threshold and spills to disk beyond it. Two effects follow. First, memory pressure inside the backend running the batch. Second, a commit-time burst: all those checks execute at once, so COMMIT — the operation everyone assumes is instant — can run for a long time doing index probes against the referenced tables. If your job has a commit timeout, or a connection pool with a socket read timeout, that burst is where it bites. ## Concurrency and visibility A fear worth putting to rest: deferral does not expose invalid data to anyone else. The rows written by the batch are uncommitted, so under normal isolation other sessions see either the pre-batch state or, after a successful commit, a fully valid state. What deferral relaxes is the invariant *within* the transaction — your own later statements in that transaction can read the inconsistent rows, which matters if the batch itself queries the data it is writing. The genuine concurrency cost is not deferral at all but the long transaction that deferral encourages. Holding one transaction open across millions of writes means: - Row locks on every written row are held to the end, so any concurrent writer touching the same rows blocks for the whole batch. - In PostgreSQL, the transaction pins the horizon, so dead tuples generated anywhere in the cluster cannot be reclaimed by vacuum while it runs — the classic cause of table and index bloat after a big nightly job. - Any failure at the end throws away all the work, so a batch that takes hours has no partial progress. The usual production shape is the opposite: chunk the batch into many small transactions with immediate checking, ordering the work so parents are written before children, and use deferral only for the specific chunks whose contents are genuinely cyclic. You get attributable errors, resumability, short lock windows and no giant pending-check queue. ## When blanket deferral is still right Restoring a consistent snapshot — a logical restore or a cross-table migration where every table must be replaced together — is the honest use case. There, the batch really is one atomic unit, no partial state is meaningful, and topologically sorting the load would be more fragile than deferring. Even then, prefer deferring named constraints and force an early `SET CONSTRAINTS ALL IMMEDIATE` before commit so failures are reported while you can still inspect them.

  • How would you keep a big load diagnosable while still using deferral?
    Defer only the named constraints that genuinely need it rather than ALL, and issue SET CONSTRAINTS ALL IMMEDIATE just before COMMIT so pending checks run at a point in the script you control, with the transaction still open for inspection. Better still, chunk the load into many small transactions ordered parents-before-children so most chunks need no deferral at all.
  • Does deferring constraints let other sessions read inconsistent data?
    No. The writes are uncommitted, so concurrent sessions see the pre-transaction state until commit and a valid state afterwards. The relaxation is only visible inside the writing transaction. The real concurrency cost is that the long transaction holds row locks and, in PostgreSQL, pins the cleanup horizon so dead tuples cannot be vacuumed.

saying these in an interview costs you the question

  • Believing SET CONSTRAINTS ALL DEFERRED affects constraints that were never declared DEFERRABLE
  • Assuming COMMIT is always instant and cannot raise an integrity error
  • Thinking concurrent sessions can see the intermediate invalid rows
  • Ignoring the per-row pending-check queue and its memory cost
  • Treating one giant deferred transaction as safer than many small immediate ones

context