MySQL死锁是InnoDB主动识别并打破循环等待的正常机制,主因是事务加锁顺序不一致;需用SHOW ENGINE INNODB STATUS定位,开启innodb_print_all_deadlocks记录全量日志,并通过统一锁序、缩短事务、优化索引等手段根治。

MySQL 死锁不是异常,而是 InnoDB 在高并发下主动识别并打破僵局的正常行为。它发生在两个或多个事务互相持有对方需要的锁、又同时申请对方持有的锁,形成循环等待。系统会自动回滚代价更小的那个事务,并报错 Deadlock found when trying to get lock (Error 1213)。真正要做的,是定位冲突点、理清加锁逻辑、调整执行顺序。
死锁最常见成因:事务加锁顺序不一致
这是线上 70% 以上死锁的根源,并非“偶发”,而是可复现的设计问题。
- 事务 A 先更新用户 ID=1,再更新 ID=2;事务 B 反过来先更新 ID=2,再更新 ID=1 → 两者各自持有一把锁,又在等对方那把,直接卡死
- 跨表操作时顺序混乱:事务 1 按「订单表→库存表」更新,事务 2 按「库存表→订单表」更新,同样触发循环依赖
- 即使单条 SQL,若涉及范围查询(如
WHERE status IN (1,2,3))且索引失效,可能升级为全表扫描+大量间隙锁,大幅增加交叉锁定概率
立刻定位现场:用 SHOW ENGINE INNODB STATUS\G
这是排查死锁最快速、最可靠的入口,无需重启、无需改配置,执行即得关键上下文。
- 执行后搜索 LATEST DETECTED DEADLOCK 段,只看这一块就够了
- 对比两个事务的 SQL 实际语句(注意是真实执行的 SQL,不是应用层拼的原始语句)
- 看谁在 HOLDS THE LOCK(S)、谁在 WAITING FOR THIS LOCK TO BE GRANTED,明确锁归属关系
- 观察 lock_mode 描述:如
lock_mode X locks rec but not gap是主键行锁;gap before rec或next-key lock则说明涉及间隙锁,大概率和非唯一索引或范围条件有关
让每次死锁都留下痕迹:开启全量死锁日志
默认情况下,SHOW ENGINE INNODB STATUS 只保留最后一次死锁。如果线上频繁报错却找不到日志,八成是没开全量记录。
- 临时开启(当前会话生效):
SET GLOBAL innodb_print_all_deadlocks = ON; - 永久生效:在
my.cnf的[mysqld]下添加
innodb_print_all_deadlocks = 1
log_error = /var/log/mysql/error.log - ⚠️ 注意:开启后所有死锁都会追加进 error log,务必配合日志轮转策略,避免磁盘写满
从根上减少发生:几条落地建议
日志只是“照镜子”,真正解决问题要回到业务逻辑和 SQL 设计。
- 统一关键路径的资源访问顺序:比如所有转账类事务,强制按用户 ID 小到大更新账户;所有订单履约流程,固定先锁订单再锁库存
- 避免长事务:事务内不做 RPC、不休眠、不等用户输入,尽量缩短持锁时间
- 确保 WHERE 条件走有效索引:缺失索引会导致行锁升级为间隙锁甚至表锁,显著扩大冲突面
- 必要时降低隔离级别:RR(可重复读)下间隙锁活跃,若业务允许,可评估降为 RC(读已提交),但需同步确认幻读影响
- 应用层加简单重试机制:捕获 Error 1213 后延迟几十毫秒重试一次,比彻底重构更轻量


















