MySQL自动回滚死锁事务并报错1213,应用需捕获该错误、对竞争逻辑重试1–2次(指数退避),同时开启innodb_print_all_deadlocks记录全量死锁日志,并统一多表更新顺序以根治。

死锁发生后,MySQL 会自动选一个事务回滚,另一个继续执行——你不用手动干预,但必须在应用层捕获错误并重试。
遇到 ERROR 1213 (40001): Deadlock found when trying to get lock 怎么办
这不是数据库挂了,而是 InnoDB 正常的死锁检测机制在起作用:它发现循环等待后,主动杀死(回滚)其中一个事务,让另一个顺利提交。关键是你得让业务代码“接住”这个错误。
- 必须在应用代码里捕获
1213错误码,而不是当作普通异常吞掉或直接报 500 - 推荐重试 1–2 次(带指数退避),比如延迟 10ms 后重跑整个事务逻辑
- 不要盲目重试所有 SQL;只对明确是“更新/插入竞争同一行”的业务逻辑加重试(如库存扣减、订单状态变更)
- 如果重试后还报 1213,大概率是设计问题(比如锁顺序不一致),不是偶发现象
SHOW ENGINE INNODB STATUS 能看到什么、怎么看
这条命令返回的是最近一次死锁的完整现场快照,重点看 LATEST DETECTED DEADLOCK 下方内容。它告诉你谁等谁、锁了哪些行、执行了什么 SQL。
- 搜索
*** (1) TRANSACTION和*** (2) TRANSACTION—— 分别对应两个冲突事务 - 关注每个事务里的
mysql tables in use、locked tables和LOCK WAIT行 - 最关键的是
WAITING FOR THIS LOCK TO BE GRANTED和HOLDS THE LOCK(S)两行,能还原出“谁持有了什么、又在等什么” - 注意
Trx id和OS thread id可用来关联慢日志或 binlog,但命令本身不保留历史,只存最后一次
SHOW ENGINE INNODB STATUS\G
如何让 MySQL 记录每次死锁(不只是最后一次)
默认只留最近一次,线上排查必须开启全量记录,否则错过就没了。
- 设置全局参数:
SET GLOBAL innodb_print_all_deadlocks = ON(需 SUPER 权限) - 该参数生效后,每次死锁都会写入 MySQL 错误日志(
error.log),格式清晰可 grep - 注意:日志量会增加,但远小于慢查询日志,生产环境建议始终开启
- 不需要重启 MySQL,但该参数不会持久化,记得写进
my.cnf的[mysqld]段落:
innodb_print_all_deadlocks = ON
为什么不能靠 ROLLBACK 或重启解决死锁
手动 ROLLBACK 对已发生的死锁无效——InnoDB 在报错前已经完成回滚;而重启 MySQL 更是危险操作:它会清空所有事务上下文,但无法修复业务逻辑缺陷,还会导致主从延迟、连接闪断、监控报警风暴。
- 死锁不是故障,是并发控制的正常结果;处理目标是“让业务稳”,不是“让日志静”
- 把重试逻辑写死在 DAO 层或 service 方法里,比靠 DBA 人工介入更可靠
- 真正要动配置的,只有
innodb_lock_wait_timeout(超时回滚)和innodb_deadlock_detect(默认 ON,关了反而更容易卡住) - 最隐蔽的坑:用 ORM 自动生成 SQL 时,不同接口可能以不同顺序 update 多张表(比如先改订单再改库存, vs 先改库存再改订单),这种不一致才是死锁温床
死锁日志里那几行 SQL 看着简单,但背后往往是多个模块耦合修改同一组数据——查日志只是起点,归因要落到代码调用链和资源访问顺序上。










