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 和结构化观察。
src.errcodes.18.6—errcodes.txtsrc.nodeModifyTable.18.6—nodeModifyTable.cdoc.mvcc.18— 序列化失败处理- Runtime:latest 与 pg10 均为
40001-manual-boundary-final-20260909,结构化观察见公开证据 JSON
比较版本
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"
}来源引用
定义来源
center · PostgreSQL 18 · english-manuals:7ba30bcee3df17e9903b7376a8990b2f57688e4496835e4011a2726294619699
正文语言: zh-Hans · english-manuals:7ba30bcee3df17e9903b7376a8990b2f57688e4496835e4011a2726294619699