百科 / SQL 状态码 / Class 40 事务回滚
40P01 deadlock_detected
检测到死锁
ERROR 已实测 详解 实测通过
- 条件名
deadlock_detected- 宏名称
ERRCODE_T_R_DEADLOCK_DETECTED- 最早已知存在
- 7.4
- 状态
- 有效
手册收录:8.1、8.2、8.3、8.4、9.0、9.1、9.2、9.3、9.4、9.5、9.6、10、11、12、13、14、15、16、17、18、19、20。此处仅表示同版本手册收录,运行证据仍以原核验构建为准。
版本覆盖
版本条依据已采样构建显示;灰色版本可能尚未采样,悬停可查看。手册链接随版本选择,源码和运行证据保持原核验构建。
速览 {#at-a-glance}
40P01 是 PostgreSQL 类别 40 transaction_rollback 中的 deadlock_detected 条件。它表示锁管理器发现事务之间形成等待环,因此选择一个受害事务并中止它。
主报文是 deadlock detected。服务器可能附带动态构造的等待图 detail、提示查看服务器日志的 hint,以及说明被中断语句的上下文。进程 ID、事务 ID 和具体等待图都随运行变化;应按 SQLSTATE 分支并保存结构化字段,不要匹配这些动态值。
代表性案例先让两个会话分别持有相反的行锁,再用 threading.Barrier 放行两个交叉请求,同时由独立观察会话采样 pg_stat_activity 的 wait_event_type=Lock。观察会话不负责放行请求。一个会话收到 40P01 后事务为 INERROR;幸存会话完成操作,随后两个会话都回到 IDLE。新的事务按顺序取得两把行锁并提交完整重试。
运行 40P01-manual-boundary-final-20260909 在 PostgreSQL 18.6 和隔离的 PostgreSQL 10.21 上均通过。逐目标断言和结构化观察保存在公开证据 JSON中。
含义与触发路径 {#meaning}
死锁需要等待图中出现环。常见形式是会话 A 锁住第 1 行后请求第 2 行,同时会话 B 锁住第 2 行后请求第 1 行。PostgreSQL 的死锁检测器会在配置的 deadlock timeout 到期后检查锁图,DeadLockReport 随后报告该条件。
这个错误说明事务协调失败,不表示行数据损坏。受害事务的全部工作都会中止。另一个事务可能在检测器打破环后继续,但它成功完成并不意味着受害事务的预期工作已经应用。
40P01 不等于最终成功的锁等待,也不同于由 statement timeout 引起的 57014。本案例观察到的 pg_stat_activity 锁等待用于证明死锁准备顺序;服务器返回的 deadlock detected 才是最终条件。
报文与诊断 {#messages}
下面的调度是代表性操作。应在两个连接上分别执行会话 A 和 B;两个会话先各自持有一把行锁,随后由 barrier 放行交叉请求,独立观察会话在请求等待期间采样两个 backend。观察会话不是放行条件。
CREATE TABLE locks(id integer PRIMARY KEY, marker text NOT NULL);
INSERT INTO locks VALUES (1, 'seed-1'), (2, 'seed-2');
-- 会话 A:开始事务、设置 deadlock_timeout,并锁住 id = 1。
BEGIN;
SET deadlock_timeout = '100ms';
UPDATE locks SET marker = 'first-1' WHERE id = 1;
-- 会话 B:开始事务、设置 deadlock_timeout,并锁住 id = 2。
BEGIN;
SET deadlock_timeout = '100ms';
UPDATE locks SET marker = 'second-2' WHERE id = 2;
-- 明确的 barrier 让两个请求同时进入锁循环。
UPDATE locks SET marker = 'first-2' WHERE id = 2;
UPDATE locks SET marker = 'second-1' WHERE id = 1;
-- 受害事务必须 ROLLBACK;幸存事务可以 COMMIT。
ROLLBACK;
COMMIT;
-- 新的重试连接按同一顺序取得锁,并验证两行。
BEGIN;
UPDATE locks SET marker = 'retry-1' WHERE id = 1;
UPDATE locks SET marker = 'retry-2' WHERE id = 2;
COMMIT;
SELECT id, marker FROM locks ORDER BY id;
PostgreSQL 18.6 对受害事务返回的形状为:
SQLSTATE: 40P01
severity: ERROR
message_primary: deadlock detected
message_detail: Process <pid-a> waits for ShareLock on transaction <xid-b>; blocked by process <pid-b>.
Process <pid-b> waits for ShareLock on transaction <xid-a>; blocked by process <pid-a>.
message_hint: See server log for query details.
context: while updating tuple (0,2) in relation "locks"
source: deadlock.c / DeadLockReport / line 1138
PG10 运行得到相同的主报文和字段,但源码行号随版本变化。detail 来自实时等待图,因此此处用运行时占位符表示进程和事务标识。hint 并不保证客户端一定收到对应的服务器日志条目,它是在日志已配置时指向调查位置。
诊断 {#diagnosis}
记录 SQLSTATE、严重级别、主报文、detail、hint、上下文、失败语句、backend PID 和事务状态。在等待仍存在时检查 pg_stat_activity 及相关锁视图。有用的观察应包含 wait_event_type=Lock、等待事件、当前查询和参与的 PID;单纯 sleep 不能建立死锁证据。
应根据实际应用代码及所有访问同一行或 advisory lock 的路径整理加锁顺序,并检查触发器、外键、索引或后台 worker 是否取得了额外的锁。deadlock_timeout 只控制何时检测,调大它会改变检测延迟,不能消除等待环。
错误发生后,受害连接必须先 ROLLBACK,因为此时为 INERROR;幸存连接在提交前可能为 INTRANS。runner 验证了显式清理后两者都回到 IDLE,并在重试前读取了数据。
处理与修复 {#response}
回滚受害事务并释放其锁,再选择完整修复:
- 让所有代码路径按一致顺序取得同一组锁,最好显式排序键值。
- 缩短事务,不要在持有数据库锁时等待外部工作。
- 只有在操作可重复且结果具备幂等语义时,才从头重试整个事务,包括读取和加锁。
- 重试后验证已提交的业务结果;幸存事务的部分更新不能证明受害事务的工作成功。
本案例的修复按顺序锁定第 1 行和第 2 行,提交 retry-1、retry-2,并在连接为 IDLE 时读取两行。生产实现仍需有限的重试预算和应用级幂等键;单凭 SQLSTATE 不能判断重复操作是否安全。
版本与边界 {#versions}
目录在 PostgreSQL 7.4 的锁定定义中已观察到 40P01,并持续到 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 取决于工作负载与设置;本页采用 SQLSTATE 及事务回滚要求作为兼容边界,而不是固定的进程 ID detail。
相关 {#related}
40001 — serialization_failure 同样要求从新快照开始重试完整事务。57014 — query_canceled 表示取消,不表示锁环。23503 — foreign_key_violation 和 23505 — unique_violation 是可能出现在锁顺序需要复核的事务中的完整性条件。25P02 — in_failed_sql_transaction 是受害事务中止后出现的后续状态。
来源 {#sources}
结构化证据记录在公开证据 JSON中。所有源码记录固定到 PostgreSQL commit 724edf9bde9d356724ad384a2e196edc3c9f80f7;运行记录保留各目标的精确 ID 和结构化观察。
src.errcodes.18.6—errcodes.txtsrc.deadlock.18.6—deadlock.cdoc.mvcc.18— 死锁与序列化失败- Runtime:latest 与 pg10 均为
40P01-manual-boundary-final-20260909,结构化观察见公开证据 JSON
来源
- 核验材料 verify/cases/40P01/cases.json
- 核验材料 doc/src/sgml/mvcc.sgml
- 核验材料 doc/src/sgml/protocol.sgml
- 核验材料 sources/manifest.lock.json
- 核验材料 verify/cases/40P01/snippets.json
- 核验材料 src/backend/storage/lmgr/deadlock.c 第 1071–1138 行
- 核验材料 src/backend/utils/errcodes.txt
证据
断言
每条断言都写明了是怎么核实的,以及它不覆盖什么。这一层是核验时留下的原始英文记录,照原样呈现,未经翻译。
-
40P01 is the deadlock_detected condition in Class 40 transaction_rollback.
-
The deadlock detector reports deadlock detected with a dynamically assembled wait-graph detail and the hint See server log for query details.
-
An explicit two-session lock cycle produced one 40P01 victim in INERROR, left one coherent survivor transaction, and allowed the victim to recover with ROLLBACK.
-
After the deadlock cleanup, an ordered retry transaction updated both rows, committed, and produced retry-1 and retry-2.
-
The locked catalogue records 40P01 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 | 案例:two_session_lock_cycle |
| pg10 | 10.21 (Debian 10.21-1.pgdg90+1) | passed | 案例:two_session_lock_cycle |
| latest This run proves the registry BEGIN/COMMIT/ROLLBACK statements for both lock-cycle sessions and the ordered retry execute with autocommit=True; worker synchronization still uses barriers and observed lock waits. |
18.6 (Homebrew) | passed | 案例:two_session_lock_cycle |
| pg10 This run proves the registry BEGIN/COMMIT/ROLLBACK statements for both lock-cycle sessions and the ordered retry execute with autocommit=True; worker synchronization still uses barriers and observed lock waits. |
10.21 (Debian 10.21-1.pgdg90+1) | passed | 案例:two_session_lock_cycle |
报文模板
源码里的格式串,不是某一次运行的输出。%s 之类是占位符,实际报文会填入对象名与取值。适用范围一栏是核验时留下的原始英文记录,未经翻译。
deadlock detected
%s
See server log for query details.
来源: 来源:src/backend/storage/lmgr/deadlock.c(lines 1071-1138) @ REL_18_6
适用范围:The source passes the assembled client wait graph through errdetail_internal("%s", clientbuf.data); process, transaction, relation, and tuple values vary by run.
可复现案例
在一次性实例上执行过的场景。其中 1 个附有可执行 SQL,正文相应小节里给出。
two_session_lock_cycle PG 10 / 18 有 SQL
前置条件
- Two sessions
- Two rows
- Both sessions acquire one row lock before attempting the other
触发
Each session requests the row held by the other, using a barrier and observed lock waits.
断言
- One statement receives 40P01
- The victim transaction is rolled back
- The surviving session can finish with one coherent update per row
- A post-victim retry takes locks in order and commits the complete repair
处置
Keep lock order consistent; after rollback, retry the complete idempotent transaction in that order and verify its committed result.
清理
Roll back workers and drop the case schema.
同类 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 起已知 |
| 检测到死锁,事务因等待环被选为受害者中止。 | 有效 | |||