死锁发生后,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 看着简单,但背后往往是多个模块耦合修改同一组数据——查日志只是起点,归因要落到代码调用链和资源访问顺序上。
