Skip to content

40001 — could not serialize access

PostgreSQL raises 40001 when a REPEATABLE READ or SERIALIZABLE transaction tries to update a row another transaction changed and committed after your snapshot began: it refuses the write rather than lose an update, and the fix is to retry the whole transaction.

Reproduce it

Two sessions. The -- [A] and -- [B] markers say who runs each statement, top to bottom.

sql
-- [A] open a snapshot and read the row
BEGIN ISOLATION LEVEL REPEATABLE READ;
SELECT balance FROM accounts WHERE id = 1;   -- sees 100

-- [B] change that same row and commit
UPDATE accounts SET balance = 150 WHERE id = 1;

-- [A] now try to write the row you read
UPDATE accounts SET balance = 200 WHERE id = 1;

A's last statement fails with ERROR: 40001: could not serialize access due to concurrent update. A's snapshot still shows 100, but the row is really 150 now, so writing 200 would silently erase B's change. Rather than let you lose that update, PostgreSQL aborts A. There's nothing to repair in the data, so you retry the transaction, and the second attempt reads the fresh value before it writes.

Run it yourself

This is the lead example on the Repeatable Read lesson, rendered there as a verified transcript against a real database. The retry loop that turns this failure into a safe write lives in Retrying serialization failures, and Serializable is the stricter level that raises the same 40001 for a wider set of conflicts.

The MySQL side

MySQL has no exact analog. InnoDB's SERIALIZABLE is lock-based rather than snapshot-based, so the same contention surfaces as a wait, not a serialization error. Depending on timing you'll hit a deadlock (1213) or a lock-wait timeout (1205) instead.

MIT Licensed · Every transcript on this site was generated by a real database run against MySQL 8.4.11 and PostgreSQL 18.4 at c52e5c9, and re-proven through psycopg and PyMySQL.