SQL 如何排查“死锁” Deadlock detected 的详细日志分析

5次阅读

postgresql死锁日志需同时满足log_min_messages≥Error且log_lock_waits=on,核心是连续三段式输出:ERROR行+两个DETAIL块,分别描述victim事务和阻塞事务的进程号、SQL、等待锁、持有锁及物理上下文。

SQL 如何排查“死锁” Deadlock detected 的详细日志分析

怎么看 PostgreSQL 的 deadlock 日志条目

PostgreSQL 默认在 log_min_messagesERRORlog_lock_waits 开启时,才会记录死锁事件。真正关键的是日志里连续出现的三段式输出:一个 ERROR: deadlock detected 错误行,紧接着是两个(或多个)DETAIL: 块,分别描述被选为 victim 的事务和另一个/多个阻塞它的事务。

重点看每段 DETAIL: 里的这几项:

  • Process XXXX: UPDATE t1 SET x = 1 WHERE id = 100 —— 进程号 + 当前正在执行的 SQL
  • Waiting for ShareLock on transaction yyYYY —— 它卡在等哪个事务释放锁
  • Process ZZZZ: select * FROM t2 WHERE id = 200 FOR UPDATE —— 另一个进程的 SQL,它持有了前者需要的锁
  • Context: while updating tuple (0,1) in relation "t1" —— 具体到页和行的物理位置(对排查热点行很有用)

如何定位死锁涉及的表、索引与事务顺序

从日志中提取出所有出现的 UPDATE/delete/SELECT ... FOR UPDATE 语句,逐条检查它们是否:

  • 操作了相同表但顺序相反:比如事务 A 先更新 t1 再更新 t2,事务 B 却先 t2t1 —— 这是最常见根源
  • 用了不同索引路径:同一条 UPDATE 在不同事务中因 WHERE 条件走不同索引,导致加锁顺序不一致
  • 隐式锁升级:例如 UPDATE 本该只锁匹配行,但因缺失索引导致全表扫描,实际锁了整页甚至整个二级索引
  • 触发器或函数调用引入了额外 DML:日志里没显示,但实际执行流中悄悄改了另一张表

d+ 表名 查索引定义,用 EXPLAIN (ANALYZE, BUFFERS) 对比各条 SQL 的执行计划,确认锁范围是否预期一致。

mysql 的 InnoDB deadlock log 怎么读(show engine innodb status 输出)

MySQL 不写 error log,而是把最近一次死锁详情塞进 SHOW ENGINE INNODB STATUSLATEST DETECTED DEADLOCK 区块。注意这个输出只保留最后一次,要抓实时日志得靠定期轮询或开启 innodb_print_all_deadlocks = ON(写入 error log)。

关键字段包括:

  • *** (1) TRANSACTION:*** (2) TRANSACTION: —— 两个冲突事务的 ID、隔离级别、等待时间
  • *** (1) HOLDS THE LOCK(S): —— 它已经持有的 record lock / gap lock 范围(如 0x7f8a1c012a80 是锁结构地址,重点看后面 index `idx_a` of table `db`.`t`
  • *** (1) WAITING FOR this LOCK TO BE GRANTED: —— 它正等待的锁类型和索引键值(如 lock_mode X locks rec but not gap waiting + 0: len 4; hex 80000064; asc d;;
  • 最后的 WE ROLL BACK TRANSACTION (1) —— 明确谁被干掉了

十六进制键值(如 80000064)需转成十进制理解:这是有符号 int 的补码表示,80000064 → -2147483548?不对,实际是 0x64 = 100,前面 800000 是符号位填充,直接取低 4 字节后转整数即可(可用 python int('64', 16) 快速验证)。

为什么开了 log_lock_waits 还看不到死锁日志

不是所有“锁等待”都会升级成死锁,更不是所有死锁都一定被记录——常见遗漏点:

  • log_lock_waits = on 只记录**超时等待**(由 lock_timeout 触发),而死锁检测是独立机制,必须同时设 log_min_messages = 'error' 或更低(如 warning)才能捕获 deadlock detected
  • 日志输出目标被重定向:检查 logging_collector = on,以及 log_destination 是否包含 stderrcsvlog,避免只写到控制台却没落盘
  • 事务在死锁前已断开:客户端崩溃或网络中断导致 backend 进程提前退出,PostgreSQL 可能来不及写完整日志
  • 使用了逻辑复制或 FDW:跨节点事务的锁不参与本地死锁检测,这类“分布式死锁”不会触发本地日志

最稳妥的方式是配合 pg_stat_activity 实时查 state = 'active'wait_event_type = 'Lock' 的会话,并结合 pg_locks 关联锁对象,而不是只依赖日志回溯。

text=ZqhQzanResources