选择 打开 改范围 完整检索页

pgsql.cc 提供对 postgresql.org 官网内容的中文翻译,由 Pigsty 团队维护。

百科 / 错误代码 / Class 40 事务回滚

40P01 deadlock_detected

检测到死锁

ERROR 已实测 详解 实测通过

类别
Class 40 事务回滚
严重等级
ERROR
条件名
deadlock_detected
宏名称
ERRCODE_T_R_DEADLOCK_DETECTED
启用版本
7.4
状态
活跃

版本覆盖

速览

40P01 是 PostgreSQL 类别 40 transaction_rollback 中的 deadlock_detected 条件。它表示锁管理器发现事务之间形成等待环,因此选择一个受害事务并中止它。

主报文是 deadlock detected。服务器可能附带动态构造的等待图 detail、提示查看服务器日志的 hint,以及说明被中断语句的上下文。进程 ID、事务 ID 和具体等待图都随运行变化;应按 SQLSTATE 分支并保存结构化字段,不要匹配这些动态值。

代表性案例先让两个会话分别持有相反的行锁,再用 threading.Barrier 放行两个交叉请求,同时由独立观察会话采样 pg_stat_activitywait_event_type=Lock。观察会话不负责放行请求。一个会话收到 40P01 后事务为 INERROR;幸存会话完成操作,随后两个会话都回到 IDLE。新的事务按顺序取得两把行锁并提交完整重试。

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

含义与触发路径

死锁需要等待图中出现环。常见形式是会话 A 锁住第 1 行后请求第 2 行,同时会话 B 锁住第 2 行后请求第 1 行。PostgreSQL 的死锁检测器会在配置的 deadlock timeout 到期后检查锁图,DeadLockReport 随后报告该条件。

这个错误说明事务协调失败,不表示行数据损坏。受害事务的全部工作都会中止。另一个事务可能在检测器打破环后继续,但它成功完成并不意味着受害事务的预期工作已经应用。

40P01 不等于最终成功的锁等待,也不同于由 statement timeout 引起的 57014。本案例观察到的 pg_stat_activity 锁等待用于证明死锁准备顺序;服务器返回的 deadlock detected 才是最终条件。

报文与诊断

下面的调度是代表性操作。应在两个连接上分别执行会话 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 并不保证客户端一定收到对应的服务器日志条目,它是在日志已配置时指向调查位置。

报文模板

源码里的格式串,不是某一次运行的输出。%s 之类是占位符,实际报文会填入对象名与取值。适用范围一栏是核验时留下的原始英文记录,未经翻译。

主消息 deadlock detected
DETAIL %s
HINT 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.

诊断

记录 SQLSTATE、严重级别、主报文、detail、hint、上下文、失败语句、backend PID 和事务状态。在等待仍存在时检查 pg_stat_activity 及相关锁视图。有用的观察应包含 wait_event_type=Lock、等待事件、当前查询和参与的 PID;单纯 sleep 不能建立死锁证据。

应根据实际应用代码及所有访问同一行或 advisory lock 的路径整理加锁顺序,并检查触发器、外键、索引或后台 worker 是否取得了额外的锁。deadlock_timeout 只控制何时检测,调大它会改变检测延迟,不能消除等待环。

错误发生后,受害连接必须先 ROLLBACK,因为此时为 INERROR;幸存连接在提交前可能为 INTRANS。runner 验证了显式清理后两者都回到 IDLE,并在重试前读取了数据。

处理与修复

回滚受害事务并释放其锁,再选择完整修复:

  • 让所有代码路径按一致顺序取得同一组锁,最好显式排序键值。
  • 缩短事务,不要在持有数据库锁时等待外部工作。
  • 只有在操作可重复且结果具备幂等语义时,才从头重试整个事务,包括读取和加锁。
  • 重试后验证已提交的业务结果;幸存事务的部分更新不能证明受害事务的工作成功。

本案例的修复按顺序锁定第 1 行和第 2 行,提交 retry-1retry-2,并在连接为 IDLE 时读取两行。生产实现仍需有限的重试预算和应用级幂等键;单凭 SQLSTATE 不能判断重复操作是否安全。

可复现案例

在一次性实例上执行过的场景。其中 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.

版本与边界

目录在 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。

来源

证据

断言

每条断言都写明了是怎么核实的,以及它不覆盖什么。这一层是核验时留下的原始英文记录,照原样呈现,未经翻译。

运行记录

目标服务器版本结果覆盖案例
latest 18.6 (Homebrew) passed two_session_lock_cycle
pg10 10.21 (Debian 10.21-1.pgdg90+1) passed two_session_lock_cycle
latest 18.6 (Homebrew) passed two_session_lock_cycle
pg10 10.21 (Debian 10.21-1.pgdg90+1) passed two_session_lock_cycle

同类错误代码

Class 40 事务回滚 下的其他成员。