Diagnostic Queries
Symptoms
A REPEATABLE READ or SERIALIZABLE transaction tried to update a row that another transaction modified and committed after this transaction’s snapshot was taken. To preserve isolation, PostgreSQL aborts your transaction rather than overwrite the change.
- The transaction is aborted with SQLSTATE 40001 and must be retried.
- Only happens under
REPEATABLE READorSERIALIZABLEisolation. - Common on hot rows (counters, balances) updated by many sessions.
What the server log shows
ERROR: could not serialize access due to concurrent update
STATEMENT: UPDATE accounts SET balance = balance - 100 WHERE id = 1;
Why PostgreSQL raises this — what the manual says
Section 13.2.2 Repeatable Read Isolation Level:
“a repeatable read transaction cannot modify or lock rows changed by other transactions after the repeatable read transaction began.”
Under snapshot isolation, each transaction sees a frozen view of the database. If a row it wants to write was changed by a concurrent committed transaction, applying the write would violate isolation, so PostgreSQL raises 40001. This is a normal, expected outcome under high-isolation levels — the correct response is to retry.
Common causes
- Concurrent updates to the same row under
REPEATABLE READ/SERIALIZABLE. - Read-modify-write patterns without retry logic.
- Hot rows (counters, balances) updated by many sessions.
How to fix it
- Catch 40001 and retry the whole transaction — this is the intended pattern.
- Keep high-isolation transactions short to shrink the conflict window.
- For hot counters, consider
READ COMMITTEDwith row locks, or aggregate writes. - Add jittered backoff between retries to avoid thundering herds.
Related & next steps
Reference: PostgreSQL 18 Section 13.2.2 “Repeatable Read Isolation Level”.
Thanks — noted. This helps keep the database accurate.