MySQL检测到死锁后自动回滚代价较小事务并报错1213,需立即执行SHOW ENGINE INNODB STATUS\G定位LATEST DETECTED DEADLOCK段,比对两事务的SQL、HOLDS/WAITING锁信息及lock_mode类型,结合innodb_print_all_deadlocks开启全量日志辅助分析,根因多为事务加锁顺序不一致或索引失效导致。

死锁发生时,先别慌着改代码
MySQL 检测到死锁后会自动回滚其中一个事务,并抛出 Deadlock found when trying to get lock 错误。这不是数据库崩溃,也不是配置错误,而是 InnoDB 在正常工作——它主动打破了僵局。真正要做的,是定位哪两条 SQL 在“抢同一组资源”,以及为什么它们会以冲突顺序加锁。
用 SHOW ENGINE INNODB STATUS\G 提取关键现场信息
这是排查死锁最直接、最不可跳过的一步。执行后,在输出中搜索 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 = 1log_error = /var/log/mysql/error.log
注意:开启后所有死锁都会追加进 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;
这种交叉更新,只要 A 拿了 orders 行、B 拿了 users 行,下一步就必然卡住。解决方式不是加重试,而是强制所有业务路径按统一顺序操作表:比如永远先 orders,再 users,再 logs。
容易被忽略的点:
– ORM 自动生成的 SQL(如 GORM 的 struct 字段顺序)可能影响 where 条件顺序
– 同一事务内多个 UPDATE 语句的执行顺序,必须和业务逻辑解耦、可预测
– 即使是单表多行更新,若 where 条件未走索引导致全表扫描,也会让“顺序”失去意义——因为锁住了整张表


















