mysql死锁报错为“deadlock found when trying to get lock; try restarting transaction”(error 1213/sqlstate 40001),是innodb检测到循环等待后主动回滚代价小的事务,需应用层捕获并重试新事务。

MySQL死锁报错长什么样?先确认是不是真死锁
遇到 Deadlock found when trying to get lock; try restarting transaction,别急着改代码。MySQL 的死锁检测是精确的,但这个错误只说明「当前事务被选为牺牲者」,不等于业务逻辑一定有缺陷——可能是并发高峰下两个合法事务恰好撞上了资源顺序。
关键判断点:
- 错误中通常带
Transaction X is waiting for lock on ...和Transaction Y has locked ...两段信息,这是 MySQL 自动记录的死锁环快照,必须看 - 如果同一段 SQL 在低并发时从不报错,高并发下高频出现,大概率是锁粒度或执行顺序问题,不是数据异常
-
SHOW ENGINE INNODB STATUS\G输出里的LATEST DETECTED DEADLOCK区域才是权威来源,应用日志里的错误只是结果
怎么看懂 SHOW ENGINE INNODB STATUS 里的死锁详情
执行 SHOW ENGINE INNODB STATUS\G 后,重点盯住最后的 LATEST DETECTED DEADLOCK 块。它不是日志,而是 InnoDB 内存中最近一次死锁的完整现场还原。
典型结构里要抓三个信息:
-
*** (1) TRANSACTION:—— 被回滚的事务(牺牲者),记下它的TRANSACTION id和mysql tables in use -
*** (1) HOLDS THE LOCK(S):—— 它已经持有哪些锁(比如RECORD LOCKS space id 123 page no 456 n bits 72) -
*** (2) WAITING FOR THIS LOCK TO BE GRANTED:—— 另一个事务在等什么锁,而这个锁正被事务(1)拿着
注意:锁描述里的 space id 和 page no 对应表空间和页号,用 SELECT * FROM INFORMATION_SCHEMA.INNODB_SYS_TABLES WHERE SPACE = 123; 可反查是哪张表;n bits 72 表示这页上有 72 个记录锁,说明可能锁了范围
常见死锁模式及对应修复方式
90% 的线上死锁来自这几类可预测场景,不是随机事件:
-
无索引导致全表扫描加锁:UPDATE 或 DELETE 语句 WHERE 条件没走索引,InnoDB 会为所有扫描过的行加 next-key 锁,极大提高碰撞概率。用
EXPLAIN确认是否走了索引,没走就加索引或改写条件 -
多语句事务中更新顺序不一致:事务 A 先更新
user_id=100再更新user_id=200,事务 B 反过来,就构成环。统一按主键/索引顺序排序后再更新,例如用ORDER BY id ASC+FOR UPDATE -
Gap Lock 意外覆盖:插入时触发唯一键冲突,InnoDB 会在冲突值前后加 gap 锁。如果业务频繁 INSERT IGNORE 或 ON DUPLICATE KEY UPDATE,且并发高,容易锁住相邻区间。可考虑用
INSERT ... SELECT预占位,或改用应用层幂等控制 -
SELECT ... FOR UPDATE 范围过大:比如
SELECT * FROM orders WHERE status = 'pending' FOR UPDATE,status 没索引或基数低,会锁整个范围。应缩小范围,或加联合索引(status, id)并用id > ?分页控制
如何验证修复是否生效
改完不能只看「不报错了」,得确认锁行为确实收敛:
- 在测试环境用
pt-deadlock-logger --run-time=60 --interval=10持续采集死锁,对比修复前后频次 - 开启
innodb_print_all_deadlocks = ON(仅测试/预发),让所有死锁写入 error log,避免只看到最后一次掩盖模式 - 用
SELECT * FROM INFORMATION_SCHEMA.INNODB_TRX观察事务持有锁的数量和时间,长时间未提交的事务是潜在隐患 - 注意:不要依赖
SHOW PROCESSLIST,它不显示锁信息;也不要只查INNODB_LOCK_WAITS,它只反映当前等待,看不到已释放的死锁历史
死锁本身不可怕,可怕的是把它当成偶发异常忽略。真正难排查的,往往是那些每小时只发生一次、但每次都在消耗连接池和拖慢响应的「低频死锁」——它们藏在 INNODB STATUS 的历史快照里,不在应用日志中显形。











