- SQLSTATE
- 40001
- Condition name
serialization_failure- Product
- PostgreSQL
- Versions
- PostgreSQL 13 to 18
- Category
- Transactions & Concurrency
- Last reviewed
- 24 October 2026
On this page
What it means
SQLSTATE 40001 means PostgreSQL rolled back a Repeatable Read or Serializable transaction because a concurrent transaction changed data it depended on; retrying the whole transaction is the documented fix.
In REPEATABLE READ and SERIALIZABLE transactions every statement sees the database as it was when the transaction started. If such a transaction tries to update, delete or lock a row that another transaction changed and committed after that point, PostgreSQL cannot apply the change without breaking that view, so it raises could not serialize access due to concurrent update. Under SERIALIZABLE, PostgreSQL also tracks what each transaction read; when a set of concurrent transactions could not have produced the same result in any serial order, one of them fails with could not serialize access due to read/write dependencies among transactions.
The documentation is explicit that applications using these isolation levels must be prepared to retry: the message text varies, but the SQLSTATE is always 40001 (serialization_failure). On the retry the transaction sees the other transaction's committed change from the start, so there is no conflict. Read-only transactions never fail with the Repeatable Read form. The default level, READ COMMITTED, does not raise these errors; it waits for the other transaction and then works on the newest row version.
The same SQLSTATE is also used when a query on a hot standby is cancelled because it conflicts with changes being replayed from the primary. That case has a different message ("canceling statement due to conflict with recovery") and different fixes, covered in the Hot Standby chapter of the documentation.
Common causes
- Two REPEATABLE READ or SERIALIZABLE transactions update the same row, and the one that commits second fails.
- A SERIALIZABLE transaction reads rows that another concurrent transaction changes, and the combination has no serial order (for example the documentation's two transactions that each insert a total computed from the other's rows).
- An application or framework sets the isolation level to SERIALIZABLE for every transaction but has no retry loop.
- Hot rows (counters, balances, stock levels) updated by many concurrent serializable transactions.
- Long queries on a hot standby that conflict with WAL replay (same SQLSTATE, different message).
Diagnose it
Read-only These queries only read the system catalogs (pg_catalog, information_schema) and statistics views such as pg_stat_activity and pg_locks. They do not change data, roles or settings, so they are safe to run first.
Which isolation level is the transaction using?
SELECT current_setting('transaction_isolation') AS isolation,
current_setting('default_transaction_isolation') AS default_isolation; read committed is the default and never raises this error. repeatable read or serializable means the application or a setting chose a stricter level. Run it inside the transaction to see the level that transaction actually uses.
Is a stricter default set for a role or database?
SELECT r.rolname, d.datname, s.setconfig
FROM pg_db_role_setting AS s
LEFT JOIN pg_roles AS r ON r.oid = s.setrole
LEFT JOIN pg_database AS d ON d.oid = s.setdatabase; Look for default_transaction_isolation=serializable or repeatable read in setconfig. A blank rolname or datname means the setting applies to all roles or all databases.
On a standby: are queries being cancelled by recovery conflicts?
SELECT datname, confl_snapshot, confl_lock, confl_bufferpin, confl_deadlock
FROM pg_stat_database_conflicts
ORDER BY datname; Non-zero counters on a standby mean queries were cancelled because of WAL replay; confl_snapshot is the usual one. On a primary these are always zero.
How to fix it
Each fix is labelled with what it changes. Try read-only checks and the least invasive fix first, and test changes outside production.
Retry the whole transaction
Client or network sideThe documented fix. Catch SQLSTATE 40001 (and usually 40P01), roll back, and run the complete transaction again from its first statement, including the reads it based its decisions on. Limit the number of attempts and add a short random delay. Do not retry only the failing statement: the reads earlier in the transaction are what became out of date.
Use READ COMMITTED with explicit row locks where full serializability is not needed
Changes dataFor a simple read-modify-write on known rows, READ COMMITTED plus FOR UPDATE makes the second transaction wait and then see the committed value, instead of failing. This is a design choice: it gives up the protection Serializable gives against anomalies between different rows.
BEGIN ISOLATION LEVEL READ COMMITTED;
SELECT balance FROM accounts WHERE acctnum = 11111 FOR UPDATE;
UPDATE accounts SET balance = balance - 100.00 WHERE acctnum = 11111;
COMMIT;
Run long reports as READ ONLY DEFERRABLE
Client or network sideA serializable read-only transaction declared DEFERRABLE may wait briefly at the start for a safe snapshot, and after that it cannot fail with a serialization error or cause others to fail. It suits long reports that must see consistent data.
BEGIN ISOLATION LEVEL SERIALIZABLE READ ONLY DEFERRABLE;
SELECT class, sum(value) FROM mytab GROUP BY class;
COMMIT;
Reduce contention on hot rows
Client or network sideKeep serializable transactions short, touch as few rows as possible, and avoid a single counter row that every transaction updates. Index the columns used to find rows: under Serializable, a sequential scan reads (and so depends on) the whole table, which causes more failures.
Examples
Repeatable Read: two transactions update the same row
Shown as documented in the PostgreSQL documentation (Transaction Isolation, Repeatable Read Isolation Level). This example needs a running PostgreSQL server (two concurrent sessions), which PGlite does not provide, so it was not reproduced here. If session 2 is still open when session 1 updates, session 1 waits; it fails only once session 2 commits.
-- Session 1
BEGIN ISOLATION LEVEL REPEATABLE READ;
SELECT balance FROM accounts WHERE acctnum = 11111;
-- Session 2
BEGIN;
UPDATE accounts SET balance = balance - 50.00 WHERE acctnum = 11111;
COMMIT;
-- Session 1
UPDATE accounts SET balance = balance - 100.00 WHERE acctnum = 11111;
ERROR: could not serialize access due to concurrent update
Serializable: two transactions that each insert a total of the other's rows
Shown as documented in the PostgreSQL documentation (Transaction Isolation, Serializable Isolation Level): one transaction commits and the other is rolled back with this message. The documentation shows the ERROR line only; a server also sends a DETAIL line with an internal reason code and a HINT that the transaction might succeed if retried. This example needs a running PostgreSQL server (two concurrent sessions), which PGlite does not provide, so it was not reproduced here.
-- mytab holds (1, 10), (1, 20), (2, 100), (2, 200)
-- Session A
BEGIN ISOLATION LEVEL SERIALIZABLE;
SELECT SUM(value) FROM mytab WHERE class = 1; -- 30
INSERT INTO mytab VALUES (2, 30);
-- Session B
BEGIN ISOLATION LEVEL SERIALIZABLE;
SELECT SUM(value) FROM mytab WHERE class = 2; -- 300
INSERT INTO mytab VALUES (1, 300);
-- Both commit
COMMIT;
ERROR: could not serialize access due to read/write dependencies among transactions
How to prevent it
Use REPEATABLE READ or SERIALIZABLE only where the application has a retry loop around the whole transaction, ideally in one shared helper that also handles 40P01. Make transactions short and free of side effects (emails, HTTP calls) that cannot be repeated, or perform those after the commit succeeds.
Count and log serialization failures. A steady trickle is normal under Serializable; a sudden rise usually means a new hot row, a missing index or a long transaction that overlaps many others.
Common questions
How do I fix "could not serialize access due to concurrent update"?
Retry the whole transaction from the beginning when you get SQLSTATE 40001. The retry sees the other transaction's committed change, so it normally succeeds. If retries are frequent, shorten the transactions or use READ COMMITTED with SELECT ... FOR UPDATE where full serializability is not needed.
Why do I get this error when I am not using SERIALIZABLE?
REPEATABLE READ raises it too, for updates, deletes and row locks on rows changed by a transaction that committed after yours started. Check transaction_isolation inside the transaction: a framework or a role setting may have chosen the level. Under the default READ COMMITTED it does not occur.
Is it safe to retry a transaction that failed with 40001?
Yes. PostgreSQL rolled the transaction back, so none of its changes were kept. Make sure the retry repeats the reads as well as the writes, and that anything outside the database (messages, files) happens only after a successful commit.
Learn more on CodeWithSQL
Official documentation
- Transaction isolation
- Serialization failure handling
- SET TRANSACTION (READ ONLY DEFERRABLE)
- Hot standby: handling query conflicts
Last technically reviewed 24 October 2026; examples and diagnostics run on PostgreSQL 18.6 documentation; SQL-level parts in PGlite 0.5.11. SQL-level examples were run in PGlite (PostgreSQL compiled to WebAssembly); examples that need a running server are shown as documented in the PostgreSQL documentation.