MySQL死锁发生后无需立即改代码,InnoDB会自动回滚代价较小事务并报错1213;应优先执行SHOW ENGINE INNODB STATUSG定位LATEST DETECTED DEADLOCK段,比对两事务SQL、HOLDS/WAITING锁信息及lock_mode类型,结合innodb_print_all_deadlocks开启全量日志分析,根因多为加锁顺序不一致或索引失效。
MySQL 检测到死锁会自动回滚代价较小的事务,并抛出 Deadlock found when trying to get lock 错误(错误码 1213)。这不是故障,是 InnoDB 正常工作的表现——它主动打破了僵局。真正要做的,是定位哪两条 SQL 在抢同一组资源,以及为什么它们加锁顺序冲突。
SHOW ENGINE INNODB STATUSG 提取现场信息这是排查死锁最直接、不可跳过的一步。执行后,在输出中只看 LATEST DETECTED DEADLOCK 段落,重点关注三块:
query id 后紧跟着的实际 SQL,比如 UPDATE t SET c = c + 1 WHERE id = 2
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 路径可写很多团队把死锁归为“偶发”,其实只要两个事务更新同一组行但顺序不同,就一定会在足够高的并发下复现。
UPDATE orders SET status=2 WHERE id=100; → UPDATE users SET last_order_id=100 WHERE id=5;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 顺序混乱导致的隐蔽冲突——它们不会立刻报错,但会在高并发时突然爆发。