编程 MySQL 死锁不报 1213:先确认 innodb_print_all_deadlocks 有没有开

2026-09-21 21:04:15

MySQL 死锁不报 1213:先确认 innodb_print_all_deadlocks 有没有开

线上死锁很少老老实实抛 Deadlock found when trying to get lock。更常见的表现是「连接超时」「应用卡顿」「系统繁忙,请稍后再试」,排查方向一下被带到网络和连接池上。

原因是死锁发生时,两个(或多个)事务互相等待对方持有的锁。等待时间超过 innodb_lock_wait_timeout(默认 50 秒)后,MySQL 强制回滚其中一个事务,对外抛出的往往是超时类异常,而不是 1213。于是真正的问题被掩盖。

参考来源:MySQL 线上死锁排查MySQL 死锁排查指南

定位手段

1. 错误日志

前提是开了 innodb_print_all_deadlocks=1。没开的话,错误日志里没有死锁记录,SHOW ENGINE INNODB STATUS 里也只保留最近一次。

2. SHOW ENGINE INNODB STATUS

SHOW ENGINE INNODB STATUS\G

LATEST DETECTED DEADLOCK 段,里面有两个事务的 SQL、加锁顺序,以及最终被回滚的是哪个事务。

3. 累计统计

SHOW GLOBAL STATUS LIKE 'Innodb_deadlocks';

用这个值评估死锁的频发程度,判断是个例还是持续发生。

4. 锁等待现场

旧版本看 information_schemaINNODB_LOCKS / INNODB_LOCK_WAITS。新版 MySQL 已经迁移到 performance_schema,对应 data_locks / data_lock_waits

SELECT * FROM performance_schema.data_locks;
SELECT * FROM performance_schema.data_lock_waits;

5. 慢查询联动

写入类慢 SQL 会大幅拉长锁持有时间,把毫秒级的锁占用拉到秒级,引发连锁锁等待。有 pt-query-digest 就分析慢日志 TOP;没有的话直接查 performance_schema

SELECT *
FROM performance_schema.events_statements_summary_by_digest
ORDER BY SUM_TIMER_WAIT DESC
LIMIT 10;

6. 慢查询与未走索引语句

开启慢查询日志,并记录未走索引的语句(log_queries_not_using_indexes),把「慢」和「锁」两条线并起来看。

常见根因类型

  • 加锁顺序不一致(最典型):事务 A 先锁 id=1 再锁 id=2,事务 B 先锁 id=2 再锁 id=1,形成等待环。
  • 热点数据更新:多个事务同时更新同一行,例如积分表 user_points,锁冲突集中在一行上。
  • 无索引更新导致锁范围扩大order_item 表的 order_id 没有索引,一条 UPDATE 触发全表扫描,锁住大量行。
  • 外键约束带来的额外锁:外键检查会在父表/子表上加锁,容易在没意识到的地方形成等待。
  • 事务里做外部调用:事务中调用外部接口、sleep、写日志、发消息,把锁窗口拉长,形成跨系统的隐式死锁。

参数取舍

innodb_lock_wait_timeout

默认 50 秒。在网关超时 3 秒的场景下,50 秒明显过长——请求早就超时了,事务还挂在锁上。不少团队调到 5 秒或 3 秒。但也不能太小,否则会误杀正常的锁等待。建议结合接口 P99 来设定。

innodb_deadlock_detect

默认 ON,一般不要关。关掉之后死锁退化成普通锁等待,由 innodb_lock_wait_timeout 兜底:原本几毫秒就能返回 1213 的错误,变成几十秒才超时,连接池更容易被打垮。

有个案例:某项目组为了提高吞吐关掉了死锁检测,结果请求堆积把连接池拖垮,恢复花了两小时。

预防

  • 统一加锁顺序:所有事务按 id 升序加锁;或者用一次性拿锁的方式:
SELECT ... WHERE id IN (1,2) FOR UPDATE;
  • 缩小事务粒度:单事务从 begincommit 控制在 100ms 内比较健康,超过 1 秒要重点查。
  • 事务内禁止远程调用:不在事务里 sleep、发消息、调外部接口。
  • 无索引更新先补索引:减少锁范围,避免全表扫描级别的加锁。
  • 打开 innodb_print_all_deadlocks:长期统计死锁,而不是等出事时才发现日志里什么都没有。
复制全文 生成海报 MySQL 死锁 InnoDB 运维排查

推荐文章

程序员茄子在线接单