Skip to content

40001 — serialization_failure

PostgreSQL reports SQLSTATE 40001 when a serializable transaction cannot be committed consistently with a concurrent update. Roll back and retry the complete transaction from a fresh snapshot.

At a glance

40001 is PostgreSQL’s serialization_failure condition in Class 40, transaction_rollback. It tells the client that the transaction’s observed ordering cannot be serialized with a concurrent transaction. PostgreSQL aborts the conflicting transaction so the client can retry.

The representative case starts two SERIALIZABLE transactions that read the same value. The first updates and commits. The stale transaction then receives could not serialize access due to concurrent update, enters INERROR, and returns to IDLE only after ROLLBACK. A new serializable transaction reads the committed value, applies the registry’s fixed SET value = 2 operation, commits, and produces the final result; this is a transaction-boundary proof, not a test of an application business calculation.

Run 40001-manual-boundary-final-20260909 passed on PostgreSQL 18.6 and isolated PostgreSQL 10.21. The per-target assertions and structured observations are retained in the public evidence JSON.

Field Value
SQLSTATE 40001
Condition serialization_failure
Status active
Known present by 7.4
Locked snapshots 9.0.23, 9.1.24, 9.2.24, 9.3.25, 9.4.26, 9.5.25, 9.6.24, 10.23, 11.22, 12.22, 13.23, 14.24, 15.19, 16.15, 17.11, 18.6, 19beta3
Macros ERRCODE_T_R_SERIALIZATION_FAILURE
Aliases

Meaning and trigger paths

At SERIALIZABLE isolation PostgreSQL uses predicate and tuple-conflict tracking to reject a transaction when its result would depend on an order that cannot be made serial. A concurrent update of a row read by a stale transaction is one concrete path. The SQLSTATE describes the transaction outcome; it does not identify the business operation that should be retried.

The executor’s update path reports ERRCODE_T_R_SERIALIZATION_FAILURE with the source message could not serialize access due to concurrent update. Other serialization conflicts can use a different message or a recovery-conflict detail while retaining the class’s transaction-rollback meaning. Preserve the complete diagnostic and the transaction’s read/write set in application logs.

40001 differs from 23505: a duplicate requested by the user is not automatically a serialization failure, even though some concurrent key-selection designs can surface a unique violation that the application treats as retryable. Decide the retry policy from the operation’s semantics and the complete transaction history.

Messages and diagnostics

The case schedule below must use two sessions for the two initial snapshots. The final block is a new connection and a new serializable snapshot; it is the required complete retry.

CREATE TABLE serial_rows(id integer PRIMARY KEY, value integer NOT NULL);
INSERT INTO serial_rows VALUES (1, 0);

-- Open both serializable snapshots before the first commit.
BEGIN ISOLATION LEVEL SERIALIZABLE;
BEGIN ISOLATION LEVEL SERIALIZABLE;

-- Session first reads 0; session stale reads the same 0.
SELECT value FROM serial_rows WHERE id = 1;
SELECT value FROM serial_rows WHERE id = 1;

-- Session first writes 1 and commits; the stale session then writes from its old snapshot.
UPDATE serial_rows SET value = 1 WHERE id = 1;
COMMIT;
UPDATE serial_rows SET value = 2 WHERE id = 1;
-- The UPDATE reports 40001 and the transaction becomes INERROR.
ROLLBACK;

-- New retry transaction: read the fresh value, apply the operation, and commit.
BEGIN ISOLATION LEVEL SERIALIZABLE;
SELECT value FROM serial_rows WHERE id = 1;
UPDATE serial_rows SET value = 2 WHERE id = 1;
COMMIT;
SELECT value FROM serial_rows WHERE id = 1;

PostgreSQL 18.6 returned:

SQLSTATE: 40001
severity: ERROR
message_primary: could not serialize access due to concurrent update
message_detail: <none>
source: nodeModifyTable.c / ExecUpdate / line 2604

PostgreSQL 10.21 produced the same primary message with its version-specific nodeModifyTable.c line. message_detail is absent in this path; other conflict origins may attach additional fields. The SQLSTATE and the aborted transaction state are the stable retry signals.

Diagnosis

Capture SQLSTATE, severity, primary message, detail, hint, context, isolation level, the statements that formed the snapshot, and transaction status. Determine which transaction committed first and which reads became stale. The runner asserted both initial reads were 0, the first commit returned IDLE, and the stale transaction was INERROR before rollback.

Do not issue a follow-up query on the failed transaction. Roll it back, start a new transaction, and repeat the complete read/decision/write sequence. Replaying only the last UPDATE can apply a decision based on a snapshot that is no longer valid.

Response and repair

Treat 40001 as a transaction retry signal when the operation is designed for it:

  • Roll back the entire failed transaction and release its locks.
  • Begin a new transaction at the required isolation level and re-read all values used by the business decision.
  • Apply the operation again, with a bounded backoff and a maximum retry count.
  • Make the operation idempotent and verify the final business result after commit.

The representative retry read 1, wrote 2, committed with status IDLE, and a separate read observed final value 2. A fixed assignment in this disposable case does not prove that an arbitrary production calculation is safe to replay; the application must recompute from the fresh snapshot.

Versions and boundaries

The catalogue has a definition-presence observation for 40001 at PostgreSQL 7.4 and through the locked 8.4.22 pre-9.0 definitions, then in every listed formal snapshot through PostgreSQL 18.6 and the PostgreSQL 19 Beta 3 preview. This is a definition-only presence boundary, not an exact implementation introduction or runtime-use claim. The class title observation between 9.0 and 9.1 is recorded in the catalogue; the condition has no other recorded definition change in the scanned range.

The stale-update case passed on PostgreSQL 18.6 and 10.21. Message source lines and conflict details vary by release and conflict type. This evidence covers a stale serializable update and its fresh complete retry; it does not claim that every 40001 source path has the same message or that every transaction can be retried safely.

40P01deadlock_detected also aborts a transaction and can require a complete retry, but its trigger is a lock cycle. 23505unique_violation is a distinct integrity condition and is not automatically retryable. 23503foreign_key_violation can be a permanent data relationship error. 25P02in_failed_sql_transaction is the follow-on state before a failed transaction is rolled back.

Sources

Structured evidence is recorded in the public evidence JSON. Source records use PostgreSQL commit 724edf9bde9d356724ad384a2e196edc3c9f80f7; run records retain exact target IDs and structured observations.