当MySQL数据库出现死锁时,不少开发者的第一反应是立即修改代码。其实不必着急,InnoDB存储引擎自身具备处理能力——它会自动选择代价较小的事务进行回滚,并返回错误码1213。这并非系统故障,而是InnoDB的正常保护机制主动打破了死锁僵局。真正需要关注的是,找出是哪两条SQL语句在争夺同一组资源,以及它们的加锁顺序为何产生冲突。

死锁发生后不必急于修改代码
MySQL在检测到死锁后,会立即回滚代价较小的事务,并抛出错误消息 Deadlock found when trying to get lock(错误码 1213)。这实际上并不是真正的问题,而是InnoDB引擎的一种自我保护机制——它主动打破了锁等待的死局。真正需要做的是,定位是哪两条SQL语句在争夺同一组资源,并分析它们加锁顺序冲突的根本原因。
使用 SHOW ENGINE INNODB STATUSG 提取死锁现场信息
此命令是排查MySQL死锁最直接且必不可少的方法。执行后,输出信息中只需关注 LATEST DETECTED DEADLOCK 部分,重点关注以下三方面内容,缺一不可:
- 每个事务的
query id后紧跟着的实际SQL,例如UPDATE t SET c = c + 1 WHERE id = 2 - 事务 (1) 的
HOLDS THE LOCK(S)和事务 (2) 的WAITING FOR THIS LOCK TO BE GRANTED——这直接指明了谁先占据了哪些锁、谁在等待什么锁 - 锁类型描述,比如
RECORD LOCKS lock_mode X locks rec but not gap表示主键上的排他记录锁;若出现gap或next-key,说明涉及间隙锁,大概率与范围查询或非唯一索引相关
查不到死锁日志?检查 innodb_print_all_deadlocks 是否已开启
默认情况下,SHOW ENGINE INNODB STATUS 只保留最近一次死锁信息。如果线上频繁报错但日志里找不到上下文,很可能就是因为没有开启全量记录。
- 临时开启(重启失效):
SET GLOBAL innodb_print_all_deadlocks = ON; - 永久生效需写入配置文件:在
[mysqld]下添加innodb_print_all_deadlocks = 1,并确保log_error路径可写 - 注意:开启后所有死锁都会追加进 error log,若不定期清理可能会撑爆磁盘
SQL语句执行顺序不一致是死锁最常见的原因
很多团队总把死锁归为“偶发”,其实只要两个事务更新同一组行但顺序不同,就一定会在足够高的并发下复现。
- 典型例子:
事务 A:UPDATE orders SET status=2 WHERE id=100;→UPDATE users SET last_order_id=100 WHERE id=5;
事务 B:UPDATE users SET last_order_id=101 WHERE id=5;→UPDATE orders SET status=2 WHERE id=101; - 解决方式不是增加重试,而是强制统一操作顺序:比如永远先操作
orders,再操作users - 索引缺失也会放大问题:
WHERE user_id = ?若user_id没有索引,InnoDB 会执行全表扫描并锁定大量行,极易与其他事务产生交叉冲突
真正难以处理的并非单次死锁,而是那些因间隙锁、长事务或ORM自动生成SQL顺序混乱所导致的隐蔽冲突——它们不会立即报错,但会在高并发时突然爆发。
