↑↓ select↵ open⌫ change scopeOpen full search

PG.CENTER connects PostgreSQL documentation, reference, and ecosystem knowledge. Maintained by Pigsty.

Wiki / SQLSTATE

40001 — serialization_failure

40001 is PostgreSQL's serializationfailure condition in Class 40, transactionrollback. 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.

English manual coverage begins with PostgreSQL 8.1. Source history and runtime observations have their own recorded version boundaries.

Reading PG 18·Select a recorded version

aliases
Unknown
class name
Transaction Rollback
condition name
serialization_failure
lang
en
sqlstate
40001

Definition PG 18

At a glance {#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.

Meaning and trigger paths {#meaning}

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 {#messages}

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 {#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 {#response}

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 {#versions}

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.

Related {#related}

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

Sources {#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.

Compare versions

SQLSTATE evidence and recorded runtime versions

cases · serializable_stale_update
{
  "assertions": [
    "The stale writer receives 40001",
    "Its transaction is rolled back",
    "The first committed value remains before retry",
    "A fresh serializable snapshot is read, applied, and committed as a complete retry"
  ],
  "case_id": "serializable_stale_update",
  "cleanup": "Roll back workers and drop the case schema.",
  "has_snippet": true,
  "position": 0,
  "preconditions": [
    "Two sessions using SERIALIZABLE",
    "Both read the same row before either writes"
  ],
  "repair": "Retry the complete serializable transaction with bounded backoff and an idempotency policy; begin from a fresh snapshot and verify the final business result.",
  "trigger": "Commit one writer, then update the row from the stale serializable transaction.",
  "versions": [
    "10",
    "18"
  ]
}
claims · identity.class-and-condition
{
  "claim_id": "identity.class-and-condition",
  "limits": "Directory identity does not identify one serialization-conflict origin.",
  "method": "Read the Class 40 section and 40001 row in the frozen errcodes.txt snapshot.",
  "position": 0,
  "runtime": [],
  "sources": [
    "src.errcodes.18.6"
  ],
  "statement": "40001 is the serialization_failure condition in Class 40 transaction_rollback."
}
claims · mechanism.concurrent-update
{
  "claim_id": "mechanism.concurrent-update",
  "limits": "Other serialization conflicts can use different messages or source paths while retaining SQLSTATE 40001.",
  "method": "Trace the ExecUpdate concurrent-update branch and compare the diagnostic from the stale serializable writer.",
  "position": 1,
  "runtime": [],
  "sources": [
    "src.nodeModifyTable.18.6"
  ],
  "statement": "The executor update path reports ERRCODE_T_R_SERIALIZATION_FAILURE with could not serialize access due to concurrent update."
}
claims · runtime.fresh-snapshot-retry
{
  "claim_id": "runtime.fresh-snapshot-retry",
  "limits": "The fixed assignment is a controlled retry proof; it does not establish that an arbitrary business calculation is safe to replay. The run reads 1 and sets value=2; it does not test a business calculation decision.",
  "method": "Open both initial serializable snapshots before the first commit, trigger the stale update, roll it back, and assert the complete fresh read/fixed assignment/write retry and final value with registry transaction boundaries executed under autocommit=True.",
  "position": 2,
  "runtime": [],
  "sources": [
    "doc.mvcc.18",
    "case-manifest.40001",
    "runtime.40001-manual-boundary-final-20260909.latest",
    "runtime.40001-manual-boundary-final-20260909.pg10"
  ],
  "statement": "The stale serializable writer entered INERROR after 40001; after ROLLBACK, a fresh serializable transaction read 1, wrote 2, committed, and left final value 2 on both targets."
}
claims · versions.catalogue-boundary
{
  "claim_id": "versions.catalogue-boundary",
  "limits": "The first scanned release is a lower bound, not an asserted introduction version.",
  "method": "Read the manifest snapshots and definition references for the code.",
  "position": 3,
  "runtime": [],
  "sources": [
    "manifest.40001"
  ],
  "statement": "The locked catalogue records 40001 in every listed formal snapshot from 9.0.23 through 18.6 and in 19beta3; pre-9.0 history is not scanned."
}
messages · message.concurrent-update
{
  "limits": "The template is the observed executor update path; other serialization failures may carry different text or detail.",
  "message_id": "message.concurrent-update",
  "path": "",
  "position": 0,
  "raw": {
    "id": "message.concurrent-update",
    "limits": "The template is the observed executor update path; other serialization failures may carry different text or detail.",
    "primary_template": "could not serialize access due to concurrent update",
    "severity_source": "explicit ERROR",
    "sources": [
      "src.nodeModifyTable.18.6"
    ],
    "sqlstate": "40001"
  },
  "severity": "explicit ERROR",
  "sources": [
    "src.nodeModifyTable.18.6"
  ],
  "templates": [
    {
      "kind": "primary",
      "literal": "could not serialize access due to concurrent update",
      "position": 0,
      "role": "",
      "template": "could not serialize access due to concurrent update"
    }
  ]
}
runtimes · runtime.40001-registry-final-20260909.latest · 18.6 (Homebrew) · passed
{
  "cases": [
    "serializable_stale_update"
  ],
  "limits": "",
  "position": 0,
  "raw": {
    "cases": [
      "serializable_stale_update"
    ],
    "id": "runtime.40001-registry-final-20260909.latest",
    "observed": {
      "diagnostic": {
        "message_primary": "could not serialize access due to concurrent update",
        "severity": "ERROR",
        "source_file": "nodeModifyTable.c",
        "source_function": "ExecUpdate",
        "source_line": "2604",
        "sqlstate": "40001"
      },
      "final_value": 2,
      "reads": {
        "first": 0,
        "retry": 1,
        "stale": 0
      },
      "statuses": {
        "first_after_commit": "IDLE",
        "retry": "IDLE",
        "stale_after_error": "INERROR",
        "stale_after_rollback": "IDLE"
      }
    },
    "raw": "verify/results/40001-registry-final-20260909/latest/raw.jsonl",
    "raw_sha256": "6a0f6ba16f654c5a3513a19647a5ef3a3e6908802133414d1a8ccd09de6a39ac",
    "run_id": "40001-registry-final-20260909",
    "server_version": "18.6 (Homebrew)",
    "server_version_num": 180006,
    "snippet_registry": {
      "path": "verify/cases/40001/snippets.json",
      "sha256": "7ed0b0bf4dc0bb2a2141a1cf9b223b61d80c9817ca041355d1f6527fbbb98007"
    },
    "status": "passed",
    "summary": "verify/results/40001-registry-final-20260909/latest/summary.json",
    "summary_sha256": "d4d8fdec585ef6fbe571940646d91cda6568befc64e12211ee17888a6c5b2433",
    "target": "latest"
  },
  "run_id": "40001-registry-final-20260909",
  "runtime_id": "runtime.40001-registry-final-20260909.latest",
  "server_version": "18.6 (Homebrew)",
  "status": "passed",
  "target": "latest"
}
runtimes · runtime.40001-registry-final-20260909.pg10 · 10.21 (Debian 10.21-1.pgdg90+1) · passed
{
  "cases": [
    "serializable_stale_update"
  ],
  "limits": "",
  "position": 1,
  "raw": {
    "cases": [
      "serializable_stale_update"
    ],
    "id": "runtime.40001-registry-final-20260909.pg10",
    "image": "postgres@sha256:b2baf8998630663d21370da06387c950e587071bdd307ee34e661cdcc7442bcc",
    "observed": {
      "diagnostic": {
        "message_primary": "could not serialize access due to concurrent update",
        "severity": "ERROR",
        "source_file": "nodeModifyTable.c",
        "source_function": "ExecUpdate",
        "source_line": "1012",
        "sqlstate": "40001"
      },
      "final_value": 2,
      "reads": {
        "first": 0,
        "retry": 1,
        "stale": 0
      },
      "statuses": {
        "first_after_commit": "IDLE",
        "retry": "IDLE",
        "stale_after_error": "INERROR",
        "stale_after_rollback": "IDLE"
      }
    },
    "raw": "verify/results/40001-registry-final-20260909/pg10/raw.jsonl",
    "raw_sha256": "85985ffb3f1599df3bc38d65651cb2a532b329e10ab2029481733b3e8a501867",
    "run_id": "40001-registry-final-20260909",
    "server_version": "10.21 (Debian 10.21-1.pgdg90+1)",
    "server_version_num": 100021,
    "snippet_registry": {
      "path": "verify/cases/40001/snippets.json",
      "sha256": "7ed0b0bf4dc0bb2a2141a1cf9b223b61d80c9817ca041355d1f6527fbbb98007"
    },
    "status": "passed",
    "summary": "verify/results/40001-registry-final-20260909/pg10/summary.json",
    "summary_sha256": "45e3f031aa5d5cb02adc714c3cbfcb27e9d166bf884c6d8405559a60b840ba45",
    "target": "pg10"
  },
  "run_id": "40001-registry-final-20260909",
  "runtime_id": "runtime.40001-registry-final-20260909.pg10",
  "server_version": "10.21 (Debian 10.21-1.pgdg90+1)",
  "status": "passed",
  "target": "pg10"
}
runtimes · runtime.40001-manual-boundary-final-20260909.latest · 18.6 (Homebrew) · passed
{
  "cases": [
    "serializable_stale_update"
  ],
  "limits": "",
  "position": 2,
  "raw": {
    "cases": [
      "serializable_stale_update"
    ],
    "id": "runtime.40001-manual-boundary-final-20260909.latest",
    "observed": {
      "connections_autocommit": {
        "first": true,
        "retry": true,
        "stale": true
      },
      "diagnostic": {
        "message_primary": "could not serialize access due to concurrent update",
        "severity": "ERROR",
        "source_file": "nodeModifyTable.c",
        "source_function": "ExecUpdate",
        "source_line": "2604",
        "sqlstate": "40001"
      },
      "final_value": 2,
      "reads": {
        "first": 0,
        "retry": 1,
        "stale": 0
      },
      "retry_semantics": "Fresh retry read 1, then fixed SET value=2; this case does not test a business calculation.",
      "statuses": {
        "first_after_commit": "IDLE",
        "retry": "IDLE",
        "stale_after_error": "INERROR",
        "stale_after_rollback": "IDLE"
      },
      "transaction_control": "Registry BEGIN/COMMIT/ROLLBACK statements run with autocommit=True; no driver implicit BEGIN participates."
    },
    "raw": "verify/results/40001-manual-boundary-final-20260909/latest/raw.jsonl",
    "raw_sha256": "befdfdf94a5577983557d64cecdd013f3e63c4e439388d1fc54e218937378b4f",
    "run_id": "40001-manual-boundary-final-20260909",
    "server_version": "18.6 (Homebrew)",
    "server_version_num": 180006,
    "snippet_registry": {
      "path": "verify/cases/40001/snippets.json",
      "sha256": "7ed0b0bf4dc0bb2a2141a1cf9b223b61d80c9817ca041355d1f6527fbbb98007"
    },
    "status": "passed",
    "summary": "verify/results/40001-manual-boundary-final-20260909/latest/summary.json",
    "summary_sha256": "6bbcb014add8ea24c8b6ba7663dade8b26a6ed6b2f95f544f9c8dc11a90cbcdf",
    "target": "latest"
  },
  "run_id": "40001-manual-boundary-final-20260909",
  "runtime_id": "runtime.40001-manual-boundary-final-20260909.latest",
  "server_version": "18.6 (Homebrew)",
  "status": "passed",
  "target": "latest"
}
runtimes · runtime.40001-manual-boundary-final-20260909.pg10 · 10.21 (Debian 10.21-1.pgdg90+1) · passed
{
  "cases": [
    "serializable_stale_update"
  ],
  "limits": "",
  "position": 3,
  "raw": {
    "cases": [
      "serializable_stale_update"
    ],
    "id": "runtime.40001-manual-boundary-final-20260909.pg10",
    "image": "postgres@sha256:b2baf8998630663d21370da06387c950e587071bdd307ee34e661cdcc7442bcc",
    "observed": {
      "connections_autocommit": {
        "first": true,
        "retry": true,
        "stale": true
      },
      "diagnostic": {
        "message_primary": "could not serialize access due to concurrent update",
        "severity": "ERROR",
        "source_file": "nodeModifyTable.c",
        "source_function": "ExecUpdate",
        "source_line": "1012",
        "sqlstate": "40001"
      },
      "final_value": 2,
      "reads": {
        "first": 0,
        "retry": 1,
        "stale": 0
      },
      "retry_semantics": "Fresh retry read 1, then fixed SET value=2; this case does not test a business calculation.",
      "statuses": {
        "first_after_commit": "IDLE",
        "retry": "IDLE",
        "stale_after_error": "INERROR",
        "stale_after_rollback": "IDLE"
      },
      "transaction_control": "Registry BEGIN/COMMIT/ROLLBACK statements run with autocommit=True; no driver implicit BEGIN participates."
    },
    "raw": "verify/results/40001-manual-boundary-final-20260909/pg10/raw.jsonl",
    "raw_sha256": "3d761eff0e571682ef5ea48bdd8ae8c355480f8775387e667c4db707f31d715f",
    "run_id": "40001-manual-boundary-final-20260909",
    "server_version": "10.21 (Debian 10.21-1.pgdg90+1)",
    "server_version_num": 100021,
    "snippet_registry": {
      "path": "verify/cases/40001/snippets.json",
      "sha256": "7ed0b0bf4dc0bb2a2141a1cf9b223b61d80c9817ca041355d1f6527fbbb98007"
    },
    "status": "passed",
    "summary": "verify/results/40001-manual-boundary-final-20260909/pg10/summary.json",
    "summary_sha256": "156c4129cf0b1b6a1108cf73353cca11425dbf353c4aa695261aeb7d8db1d992",
    "target": "pg10"
  },
  "run_id": "40001-manual-boundary-final-20260909",
  "runtime_id": "runtime.40001-manual-boundary-final-20260909.pg10",
  "server_version": "10.21 (Debian 10.21-1.pgdg90+1)",
  "status": "passed",
  "target": "pg10"
}
sources · src.errcodes.18.6
{
  "commit": "724edf9bde9d356724ad384a2e196edc3c9f80f7",
  "docs_url": "",
  "kind": "",
  "location": "lines 329-333",
  "path": "src/backend/utils/errcodes.txt",
  "position": 0,
  "sha256": "6e8de346643ba84aa3c9c6a73360acfc7b2dfb89162c06c08ce9bf5bcd5bbcba",
  "source_id": "src.errcodes.18.6",
  "tag": "REL_18_6",
  "url": "https://github.com/postgres/postgres/blob/724edf9bde9d356724ad384a2e196edc3c9f80f7/src/backend/utils/errcodes.txt"
}
sources · src.nodeModifyTable.18.6
{
  "commit": "724edf9bde9d356724ad384a2e196edc3c9f80f7",
  "docs_url": "",
  "kind": "",
  "location": "lines 2595-2605",
  "path": "src/backend/executor/nodeModifyTable.c",
  "position": 1,
  "sha256": "0fc3cb180b3443f216142954e43d8ed5f84713d75094ce8cd2785ad8f604bd68",
  "source_id": "src.nodeModifyTable.18.6",
  "tag": "REL_18_6",
  "url": "https://github.com/postgres/postgres/blob/724edf9bde9d356724ad384a2e196edc3c9f80f7/src/backend/executor/nodeModifyTable.c#L2595-L2605"
}
sources · doc.mvcc.18
{
  "commit": "724edf9bde9d356724ad384a2e196edc3c9f80f7",
  "docs_url": "",
  "kind": "",
  "location": "serialization failure handling",
  "path": "doc/src/sgml/mvcc.sgml",
  "position": 2,
  "sha256": "4b7bf77ee0af4b86052330ff2421d567df3604b75fe7d4f522bec84fdea87340",
  "source_id": "doc.mvcc.18",
  "tag": "PG18-docs",
  "url": "https://www.postgresql.org/docs/18/mvcc-serialization-failure-handling.html"
}
sources · doc.protocol.18
{
  "commit": "724edf9bde9d356724ad384a2e196edc3c9f80f7",
  "docs_url": "",
  "kind": "",
  "location": "ErrorResponse fields",
  "path": "doc/src/sgml/protocol.sgml",
  "position": 3,
  "sha256": "745339e07ebbc2bb67d98b258e25772ada644ef26011e3cd6b3fcbb435235f89",
  "source_id": "doc.protocol.18",
  "tag": "PG18-docs",
  "url": "https://www.postgresql.org/docs/18/protocol-error-fields.html"
}
sources · manifest.40001
{
  "commit": "",
  "docs_url": "",
  "kind": "local_artifact",
  "location": "snapshots and definition_blobs entries for the 40001 definition",
  "path": "sources/manifest.lock.json",
  "position": 4,
  "sha256": "1727a275f336988ff96b4f9990a4ca253080f73fc5d316e7def165c8cf3708a8",
  "source_id": "manifest.40001",
  "tag": "",
  "url": "../sources/manifest.lock.json"
}
sources · case-manifest.40001
{
  "commit": "",
  "docs_url": "",
  "kind": "local_artifact",
  "location": "serializable_stale_update",
  "path": "verify/cases/40001/cases.json",
  "position": 5,
  "sha256": "f763521e946921e560a2e2f769507be836cefae2ae2d0ca8d8d60a9c55c9e45b",
  "source_id": "case-manifest.40001",
  "tag": "",
  "url": "../verify/cases/40001/cases.json"
}
sources · snippet-registry.40001.final
{
  "commit": "",
  "docs_url": "",
  "kind": "local_artifact",
  "location": "serializable_stale_update ordered SQL",
  "path": "verify/cases/40001/snippets.json",
  "position": 6,
  "sha256": "7ed0b0bf4dc0bb2a2141a1cf9b223b61d80c9817ca041355d1f6527fbbb98007",
  "source_id": "snippet-registry.40001.final",
  "tag": "",
  "url": "../verify/cases/40001/snippets.json"
}

Source references

Full definition and evidence JSON

Definition sources

center · PostgreSQL 18 · english-manuals:7ba30bcee3df17e9903b7376a8990b2f57688e4496835e4011a2726294619699

Text language: en · english-manuals:7ba30bcee3df17e9903b7376a8990b2f57688e4496835e4011a2726294619699