ERROR: could not serialize access due to concurrent update
ID: database/pg-could-not-serialize
Version Compatibility
| Version | Status | Introduced | Deprecated | Notes |
|---|---|---|---|---|
| 16 | active | — | — | — |
Root Cause
A transaction running at SERIALIZABLE or REPEATABLE READ isolation level attempted to modify a row that was already modified by another committed transaction since the start of the current transaction. PostgreSQL aborts the transaction to preserve snapshot isolation guarantees. The application must retry the transaction.
genericWorkarounds
-
90% success Implement automatic transaction retry with exponential backoff
Catch SQLSTATE 40001 (serialization_failure) in your application code and retry the entire transaction (not just the failed statement). Use exponential backoff: 50ms, 100ms, 200ms with jitter. Limit retries to 3-5 attempts. Most serialization failures succeed on the first retry.
-
82% success Reduce transaction scope to minimize conflict windows
Break large transactions into smaller units of work. Move read-only operations outside the SERIALIZABLE transaction. Use advisory locks (pg_advisory_lock) for application-level coordination when the same logical resource is always contended.
Dead Ends
Common approaches that don't work:
-
Downgrading isolation level to READ COMMITTED globally to avoid serialization failures
70% fail
READ COMMITTED eliminates serialization errors but introduces phantom reads and non-repeatable reads. If the application logic depends on consistent snapshots (e.g., financial calculations, inventory checks), this leads to data corruption bugs that are much harder to debug than retry logic.
-
Adding SELECT FOR UPDATE locks everywhere to prevent concurrent modifications
65% fail
Excessive pessimistic locking defeats the purpose of MVCC, reduces throughput significantly, and can introduce deadlocks. It converts optimistic concurrency control into pessimistic, negating the benefits of SERIALIZABLE isolation.