# 40001 — serialization_failure：序列化失败

> 当可串行化事务无法与并发更新形成一致的提交顺序时，PostgreSQL 会报告 SQLSTATE 40001。应回滚完整事务，并从新快照开始重试。
---

## 速览 {#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](../../data/evidence/40001.json)。

<!-- BEGIN SQLSTATE FACTS: generated by scripts/generate.py; do not edit -->

| 字段 | 值 |
| --- | --- |
| SQLSTATE | `40001` |
| 条件名 | `serialization_failure` |
| 状态 | `有效` |
| 已知存在于 | `7.4` |
| 锁定快照 | `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` |
| 宏 | `ERRCODE_T_R_SERIALIZATION_FAILURE` |
| 别名 | `—` |

<!-- source facts: data/errcodes/40001.json -->
<!-- END SQLSTATE FACTS -->

## 含义与触发路径 {#meaning}

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

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

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

## 报文与诊断 {#messages}

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

<!-- BEGIN SQLSTATE SNIPPET: serializable_stale_update -->
```sql
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;
```
<!-- END SQLSTATE SNIPPET -->

PostgreSQL 18.6 返回：

```text
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`](../40p01/) 同样会中止事务并可能要求完整重试，但触发原因是锁环。[`23505` — `unique_violation`](../23505/) 是不同的完整性条件，不能自动重试。[`23503` — `foreign_key_violation`](../23503/) 可能是持久的数据关系错误。[`25P02` — `in_failed_sql_transaction`](../25p02/) 是失败事务回滚前的后续状态。

## 来源 {#sources}

结构化证据记录在[公开证据 JSON](../../data/evidence/40001.json)中。源码记录固定到 PostgreSQL commit `724edf9bde9d356724ad384a2e196edc3c9f80f7`；运行记录保留精确目标 ID 和结构化观察。

- `src.errcodes.18.6` — [`errcodes.txt`](https://github.com/postgres/postgres/blob/724edf9bde9d356724ad384a2e196edc3c9f80f7/src/backend/utils/errcodes.txt#L329-L333)
- `src.nodeModifyTable.18.6` — [`nodeModifyTable.c`](https://github.com/postgres/postgres/blob/724edf9bde9d356724ad384a2e196edc3c9f80f7/src/backend/executor/nodeModifyTable.c#L2595-L2605)
- `doc.mvcc.18` — [序列化失败处理](https://www.postgresql.org/docs/18/mvcc-serialization-failure-handling.html)
- Runtime：latest 与 pg10 均为 `40001-manual-boundary-final-20260909`，结构化观察见[公开证据 JSON](../../data/evidence/40001.json)
