PostgreSQL · 错误码速查
PostgreSQL deadlock detected:被回滚的是整个事务,按固定顺序加锁并整事务重试
ERROR: deadlock detected(SQLSTATE 40P01)表示两个或多个事务互相等待对方持有的锁。PostgreSQL 不会一开始就检测,而是等锁满 deadlock_timeout(默认 1s)后才检查;发现环就让其中一个事务报错,整个事务回滚,另一个继续并可提交。客户端只看到进程号和事务号,完整 SQL 要看服务器日志。根治是让所有代码按同一顺序锁行;应用侧对 40P01 做整事务重试。
官方原文
deadlock detected出自 PostgreSQL 源码 src/backend/storage/lmgr/deadlock.c 的 DeadLockReport(),错误码 ERRCODE_T_R_DEADLOCK_DETECTED(errcodes.txt 中为 40P01 deadlock_detected),同时带 HINT「See server log for query details.」。
出处:PostgreSQL 源码 REL_17_STABLE:src/backend/storage/lmgr/deadlock.c(DeadLockReport) 最后核对:
| SQLSTATE | 40P01(deadlock_detected,Class 40 Transaction Rollback) | 出处 |
|---|---|---|
| 回滚范围 | 报错的整个事务回滚,不只是当前语句 | 出处 |
| deadlock_timeout | 默认 1s:等锁多久后才做死锁检测;官方称这大概是实践中想要的最小值 | 出处 |
| 官方建议 | 所有应用按一致的顺序获取多个对象上的锁;做不到就对因死锁中止的事务重试 | 出处 |
| 版本差异 | PG14.24 与 PG17.11 报错原文一致,仅源码行号不同(deadlock.c:1147 / 1130) |
盈尺实测(测试库,非官方文档):2026-11-09 PG14.24 / PG17.11 测试库,两会话反序更新 2 行:deadlock_timeout=1s 时 1.51–1.52 s 报 40P01,200ms 时 0.71 s;被回滚的会话整事务撤销,两版原文一致。 本页第一段:测试库复现输出 →
测试库复现:两个会话反序更新两行,1.5 秒后一个被回滚
2026-11-09 在 box 本机测试库(PostgreSQL 14.24 与 17.11,自造的 acct 表两行,非客户数据)开两个会话:会话 1 先 UPDATE acct … WHERE id=1,会话 2 先 UPDATE acct … WHERE id=2,各自 pg_sleep(0.5) 后再去更新对方那一行。PG17.11 上会话 1 收到(\set VERBOSITY verbose):
ERROR: 40P01: deadlock detected
DETAIL: Process 594841 waits for ShareLock on transaction 754; blocked by process 594843.
Process 594843 waits for ShareLock on transaction 753; blocked by process 594841.
HINT: See server log for query details.
CONTEXT: while updating tuple (0,2) in relation "acct"
LOCATION: DeadLockReport, deadlock.c:1130| deadlock_timeout | 被回滚的会话 | 从开始到报错(两版) | 事务结束后余额 |
|---|---|---|---|
| 1s(默认) | 会话 1 | 14.24:1.51 s;17.11:1.52 s | id1=101,id2=99 |
| 200ms | 会话 1 | 14.24:0.71 s;17.11:0.71 s | id1=102,id2=98 |
余额说明回滚范围:每轮会话 1 想把 id1 减 1、id2 加 1,会话 2 反过来。结果每轮只有会话 2 的那一笔生效(id1 +1、id2 −1),会话 1 已经执行成功的第一条 UPDATE 也一起撤销了。所以应用重试时要从 BEGIN 开始重放整个事务,不能只重试报错的那一句。
DETAIL 怎么读:ShareLock on transaction 其实是在等行锁
Process A waits for ShareLock on transaction N; blocked by process B:A 想改的行正被事务 N(B 的事务)改着,PostgreSQL 的行锁表现为「等对方事务结束」,所以写的是 transaction 而不是行号。CONTEXT: while updating tuple (0,2) in relation "acct":被回滚的那条语句卡在哪张表、哪个物理位置(ctid)的行上;ctid 会随 UPDATE/VACUUM 变化,只用于当场定位。- 客户端拿不到对方的 SQL,HINT 让你去看服务器日志。测试库默认配置(
log_lock_waits=off)下日志仍会记录Process 594841: <语句>两行,即每个进程当时在执行的语句文本;本次是 psql 一次发出的整串,所以日志里是整串事务。
-- 事后在日志里搜 SQLSTATE 或原文
-- grep -A6 'deadlock detected' postgresql-*.log
-- 现场还在等锁时(死锁形成前),只读查看谁在等谁
SELECT pid, pg_blocking_pids(pid) AS blocked_by, wait_event_type, state,
now() - xact_start AS xact_age, left(query, 80) AS query
FROM pg_stat_activity
WHERE cardinality(pg_blocking_pids(pid)) > 0;deadlock_timeout 不是超时:调小只让报错早一点,不减少死锁
名字容易误解:它不是「死锁多久后放弃」,而是「等锁多久后才花成本去检查有没有死锁」。上表里调到 200ms,报错从约 1.5 s 提前到约 0.7 s,死锁照样发生。官方文档的说法是:默认 1s 大概是实践中想要的最小值,负载重的服务器可以调大,最好比典型事务时长更长。
- 开
log_lock_waits = on后,等锁超过 deadlock_timeout 也会写一条日志,这时它同时决定「多久算慢等锁」;排查锁等待时可以临时调小,查完恢复。 - 改它需要超级用户或有 SET 权限的角色;不建议为了「快点失败」全局调小,测试只证明了报错时间变化,没有测检测本身的开销。
- 要限制等锁时间用
lock_timeout(报 55P03),和死锁是两回事。
怎么避免:固定加锁顺序 + 对 40P01 整事务重试;和 MySQL 1213 的区别
- 固定顺序:同一事务要改多行时,先
SELECT … WHERE id = ANY($1) ORDER BY id FOR UPDATE按主键顺序锁住,再更新;转账类逻辑把两个账户按 id 小的先锁。官方原话:如果两个事务按同样顺序更新行,就不会发生死锁。 - 缩短持锁时间:事务里不要夹着远程调用或等待用户输入(测试里的
pg_sleep(0.5)就是在模拟这一段)。 - 应用重试:捕获 SQLSTATE
40P01(和40001序列化失败一样属于 Class 40),回滚后整事务重放,带退避、限次数(如 3 次)。 - 和 MySQL 1213 的区别:InnoDB 默认(
innodb_deadlock_detect=ON)在加锁时立即检测,PostgreSQL 先等 deadlock_timeout 再检测,所以 PG 上死锁报错前会多出约 1 秒等待;两边都是整事务回滚,MySQL 用SHOW ENGINE INNODB STATUS看最近一次死锁,PostgreSQL 看服务器日志。
常见问题
PostgreSQL deadlock detected 是什么意思?
两个或多个事务互相等待对方持有的锁,形成环。PostgreSQL 等锁满 deadlock_timeout(默认 1s)后检测到环,让其中一个事务报 40P01 并回滚,另一个继续。
死锁后被回滚的是一条语句还是整个事务?
整个事务。测试库上被回滚的会话,第一条已执行成功的 UPDATE 也被撤销了,所以重试要从 BEGIN 开始重放。
怎么看到死锁双方的完整 SQL?
客户端只返回进程号、事务号和 CONTEXT,完整语句在服务器日志里(HINT: See server log for query details),默认配置下就会记录。
把 deadlock_timeout 调小能减少死锁吗?
不能。它只决定多久后检测,调小让报错更早出现(测试库上从约 1.5 s 变成约 0.7 s),死锁本身照样发生。根治靠固定加锁顺序。
PostgreSQL 14 升级到 17 后死锁报错有变化吗?
本次测试库对比,14.24 和 17.11 的 ERROR、DETAIL、HINT、CONTEXT 原文一致,只有源码行号不同,按 40P01 写的重试逻辑不用改。
免费 · 留邮箱领检查单
PostgreSQL 14 停更升级检查单(Excel 可直接打开)
- PG14 2026-11-12 停更,升 17 / 18 前后 19 项检查:只读预检、--check、回滚、补统计、回归
- 每项写清怎么查、通过标准、不处理会怎样(附测试库实测数字)
- 配合站上的只读预检 SQL 使用,每个业务库各跑一次
下一步
价格以对应页面当前标价为准。
出处(最后核对 2026-11-09)
- PostgreSQL 源码 REL_17_STABLE:src/backend/storage/lmgr/deadlock.c(DeadLockReport)
- PostgreSQL 17 文档 附录 A:PostgreSQL Error Codes(40P01 deadlock_detected)
- PostgreSQL 17 文档 13.3.4:Deadlocks
- PostgreSQL 17 文档 19.12:Lock Management(deadlock_timeout)
- PostgreSQL 17 文档 19.8:Error Reporting and Logging(log_lock_waits)
本页摘录并解读上面列出的官方文档,不是官方页面;官方文档更新后以官方为准。除标明「盈尺实测」的一段外,其余内容都来自官方文档。