↑↓ 选择↵ 打开⌫ 切换范围完整搜索

PG.CENTER 连接 PostgreSQL 文档、百科与生态知识。由 Pigsty 维护。

Wiki / SQLSTATE

40001 — serialization_failure:序列化失败

并发事务无法串行化,需回滚后重试。

英文手册覆盖始于 PostgreSQL 8.1. 来源历史与运行验证各自保留独立版本边界。

当前阅读 PG 18·选择有来源记录的版本

aliases
未知
class name
Transaction Rollback
condition name
serialization_failure
lang
en
sqlstate
40001

版本定义 PG 18

速览 {#at-a-glance}

40001 是 PostgreSQL 类别 40 transaction_rollback 中的 serialization_failure 条件。它告诉客户端,事务观察到的顺序无法与并发事务组成可串行化顺序,因此 PostgreSQL 中止冲突事务,让客户端重新执行。

代表性案例启动两个 SERIALIZABLE 事务,让它们读取同一值。第一个事务更新并提交;旧快照事务随后收到 could not serialize access due to concurrent update,进入 INERROR,只有 ROLLBACK 后才回到 IDLE。新的可串行化事务读取已提交值,执行 registry 中固定的 SET value = 2 操作并提交最终结果;这证明的是事务边界,不是业务计算逻辑。

运行 40001-manual-boundary-final-20260909 在 PostgreSQL 18.6 和隔离的 PostgreSQL 10.21 上均通过。逐目标断言和结构化观察见公开证据 JSON。

含义与触发路径 {#meaning}

在 SERIALIZABLE 隔离级别下,PostgreSQL 跟踪谓词和元组冲突;如果事务结果依赖一个无法串行化的顺序,就会拒绝该事务。旧事务更新之前已读取、后来被并发事务更新的行,是其中一个具体路径。SQLSTATE 说明事务结果,不说明业务操作是否应该重试。

执行器的更新路径使用 ERRCODE_T_R_SERIALIZATION_FAILURE 和 could not serialize access due to concurrent update 源码报文。其他序列化冲突可能使用不同报文,恢复冲突也可能附带 detail,但仍表达事务回滚语义。应用日志应保存完整诊断以及事务的读写集合。

40001 与 23505 不同:用户主动请求的重复值不自动成为序列化失败,即使某些并发生成键的设计会产生应用认为可重试的唯一冲突。应根据操作语义和完整事务历史决定重试策略。

报文与诊断 {#messages}

下面的调度需要两个会话分别建立最初的快照。最后一段使用新的连接和新的可串行化快照,这是完整重试的必要部分。

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

-- 在第一次提交前打开两个 SERIALIZABLE 快照。
BEGIN ISOLATION LEVEL SERIALIZABLE;
BEGIN ISOLATION LEVEL SERIALIZABLE;

-- first 会话读到 0;stale 会话也读到同一个 0。
SELECT value FROM serial_rows WHERE id = 1;
SELECT value FROM serial_rows WHERE id = 1;

-- first 会话写入 1 并提交;随后 stale 会话用旧快照写入。
UPDATE serial_rows SET value = 1 WHERE id = 1;
COMMIT;
UPDATE serial_rows SET value = 2 WHERE id = 1;
-- UPDATE 报告 40001,事务变为 INERROR。
ROLLBACK;

-- 新的重试事务:读取新值,重新执行操作并提交。
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 返回:

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 的主报文相同,nodeModifyTable.c 行号随版本变化。本路径没有 message_detail;其他冲突来源可能附带更多字段。SQLSTATE 和事务已经中止的状态是稳定的重试信号。

诊断 {#diagnosis}

记录 SQLSTATE、严重级别、主报文、detail、hint、上下文、隔离级别、建立快照的语句以及事务状态。确认哪个事务先提交,以及哪些读取已过期。runner 断言两个初始读取都是 0,first 提交后为 IDLE,stale 事务在回滚前为 INERROR。

不要在失败事务上继续查询。先回滚,再以新的事务开始,并重复完整的读取、决策和写入。只重放最后一条 UPDATE 可能基于已经失效的快照作出决定。

处理与修复 {#response}

当操作为此设计时,应把 40001 当作事务重试信号:

  • 回滚整个失败事务并释放锁。
  • 按要求的隔离级别启动新事务,重新读取业务决策依赖的所有值。
  • 使用有限退避和最大重试次数重新执行操作。
  • 让操作具备幂等性,并在提交后验证最终业务结果。

代表性重试读取 1、写入 2、以 IDLE 状态提交,独立读取观察到最终值 2。这个隔离案例中的固定赋值不能证明生产环境任意计算都可重放;应用必须从新快照重新计算。

版本与边界 {#versions}

目录在 PostgreSQL 7.4 的锁定定义中已观察到 40001,并持续到 8.4.22 的 pre-9.0 定义;随后在列出的所有正式快照直到 PostgreSQL 18.6 以及 PostgreSQL 19 Beta 3 预览中存在。这是 definition_only 的存在边界,不是确切实现引入版本或运行时使用断言。9.0 到 9.1 之间记录了类别标题变化;目录保留该变化,扫描范围内条件没有其他定义变化。

旧快照更新案例在 PostgreSQL 18.6 和 10.21 上均通过。源码行号和冲突 detail 会因版本和冲突类型变化。本证据只覆盖旧快照更新及其新的完整重试,不声称所有 40001 路径使用相同报文,也不声称所有事务都能安全重试。

相关 {#related}

40P01 — deadlock_detected 同样会中止事务并可能要求完整重试,但触发原因是锁环。23505 — unique_violation 是不同的完整性条件,不能自动重试。23503 — foreign_key_violation 可能是持久的数据关系错误。25P02 — in_failed_sql_transaction 是失败事务回滚前的后续状态。

来源 {#sources}

结构化证据记录在公开证据 JSON中。源码记录固定到 PostgreSQL commit 724edf9bde9d356724ad384a2e196edc3c9f80f7;运行记录保留精确目标 ID 和结构化观察。

比较版本

SQLSTATE 证据与已记录的运行版本

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"
}

来源引用

完整定义与证据 JSON

定义来源

center · PostgreSQL 18 · english-manuals:7ba30bcee3df17e9903b7376a8990b2f57688e4496835e4011a2726294619699

正文语言: zh-Hans · english-manuals:7ba30bcee3df17e9903b7376a8990b2f57688e4496835e4011a2726294619699