文档 / SQL 状态码 / Class 40 事务回滚
40001 serialization_failure
序列化失败
ERROR 已实测 详解 实测通过
- 条件名
serialization_failure- 宏名称
ERRCODE_T_R_SERIALIZATION_FAILURE- 启用版本
- 7.4
- 状态
- 活跃
版本覆盖
速览
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。
含义与触发路径
在 SERIALIZABLE 隔离级别下,PostgreSQL 跟踪谓词和元组冲突;如果事务结果依赖一个无法串行化的顺序,就会拒绝该事务。旧事务更新之前已读取、后来被并发事务更新的行,是其中一个具体路径。SQLSTATE 说明事务结果,不说明业务操作是否应该重试。
执行器的更新路径使用 ERRCODE_T_R_SERIALIZATION_FAILURE 和 could not serialize access due to concurrent update 源码报文。其他序列化冲突可能使用不同报文,恢复冲突也可能附带 detail,但仍表达事务回滚语义。应用日志应保存完整诊断以及事务的读写集合。
40001 与 23505 不同:用户主动请求的重复值不自动成为序列化失败,即使某些并发生成键的设计会产生应用认为可重试的唯一冲突。应根据操作语义和完整事务历史决定重试策略。
报文与诊断
下面的调度需要两个会话分别建立最初的快照。最后一段使用新的连接和新的可串行化快照,这是完整重试的必要部分。
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 和事务已经中止的状态是稳定的重试信号。
报文模板
源码里的格式串,不是某一次运行的输出。%s 之类是占位符,实际报文会填入对象名与取值。适用范围一栏是核验时留下的原始英文记录,未经翻译。
could not serialize access due to concurrent update
来源:src/backend/executor/nodeModifyTable.c(lines 2595-2605) @ REL_18_6
适用范围:The template is the observed executor update path; other serialization failures may carry different text or detail.
诊断
记录 SQLSTATE、严重级别、主报文、detail、hint、上下文、隔离级别、建立快照的语句以及事务状态。确认哪个事务先提交,以及哪些读取已过期。runner 断言两个初始读取都是 0,first 提交后为 IDLE,stale 事务在回滚前为 INERROR。
不要在失败事务上继续查询。先回滚,再以新的事务开始,并重复完整的读取、决策和写入。只重放最后一条 UPDATE 可能基于已经失效的快照作出决定。
处理与修复
当操作为此设计时,应把 40001 当作事务重试信号:
- 回滚整个失败事务并释放锁。
- 按要求的隔离级别启动新事务,重新读取业务决策依赖的所有值。
- 使用有限退避和最大重试次数重新执行操作。
- 让操作具备幂等性,并在提交后验证最终业务结果。
代表性重试读取 1、写入 2、以 IDLE 状态提交,独立读取观察到最终值 2。这个隔离案例中的固定赋值不能证明生产环境任意计算都可重放;应用必须从新快照重新计算。
可复现案例
在一次性实例上执行过的场景。其中 1 个附有可执行 SQL,正文相应小节里给出。
serializable_stale_update PG 10 / 18 有 SQL
前置条件
- Two sessions using SERIALIZABLE
- Both read the same row before either writes
触发
Commit one writer, then update the row from the stale serializable transaction.
断言
- 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
处置
Retry the complete serializable transaction with bounded backoff and an idempotency policy; begin from a fresh snapshot and verify the final business result.
清理
Roll back workers and drop the case schema.
版本与边界
目录在 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 路径使用相同报文,也不声称所有事务都能安全重试。
来源
- 核验材料 verify/cases/40001/cases.json
- 核验材料 doc/src/sgml/mvcc.sgml
- 核验材料 doc/src/sgml/protocol.sgml
- 核验材料 sources/manifest.lock.json
- 核验材料 verify/cases/40001/snippets.json
- 核验材料 src/backend/utils/errcodes.txt
- 核验材料 src/backend/executor/nodeModifyTable.c 第 2595–2605 行
证据
断言
每条断言都写明了是怎么核实的,以及它不覆盖什么。这一层是核验时留下的原始英文记录,照原样呈现,未经翻译。
-
40001 is the serialization_failure condition in Class 40 transaction_rollback.
-
The executor update path reports ERRCODE_T_R_SERIALIZATION_FAILURE with could not serialize access due to concurrent update.
-
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.
-
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.
运行记录
| 目标 | 服务器版本 | 结果 | 覆盖案例 |
|---|---|---|---|
| latest | 18.6 (Homebrew) | passed | serializable_stale_update |
| pg10 | 10.21 (Debian 10.21-1.pgdg90+1) | passed | serializable_stale_update |
| latest | 18.6 (Homebrew) | passed | serializable_stale_update |
| pg10 | 10.21 (Debian 10.21-1.pgdg90+1) | passed | serializable_stale_update |
同类 SQL 状态码
| 状态码 | 条件名 | 宏名称 | 严重等级 | 版本 |
|---|---|---|---|---|
| 40000 | transaction_rollback | ERRCODE_TRANSACTION_ROLLBACK |
ERROR | 7.4 |
| 事务回滚的类别码,具体原因见 40001 等子码。 | 活跃 | |||
| 40001 | serialization_failure | ERRCODE_T_R_SERIALIZATION_FAILURE |
ERROR | 7.4 |
| 并发事务无法串行化,需回滚后重试。 | 活跃 | |||
| 40002 | transaction_integrity_constraint_violation | ERRCODE_T_R_INTEGRITY_CONSTRAINT_VIOLATION |
ERROR | 7.4 |
| 事务级完整性约束违规的总括码,核心不直接使用。 | 活跃 | |||
| 40003 | statement_completion_unknown | ERRCODE_T_R_STATEMENT_COMPLETION_UNKNOWN |
ERROR | 7.4 |
| 语句完成状态不确定,重试前先核对业务状态。 | 活跃 | |||
| 40P01 | deadlock_detected | ERRCODE_T_R_DEADLOCK_DETECTED |
ERROR | 7.4 |
| 检测到死锁,事务因等待环被选为受害者中止。 | 活跃 | |||