首页 > Technology > 正文

MySQL 数据库死锁怎么办?排查与预防实操指南

fenij 2026-07-27 34 Technology

线上业务突然报错,日志里冒出一句 Deadlock found when trying to get lock——这就是数据库死锁。两个事务各自握着对方要的锁,谁也等不到对方放手,MySQL 只好挑一个回滚掉。死锁要是频繁起来,轻则接口超时,重则业务直接断掉。下面从排查到根治,一步步说清楚。

1. 第一步:确认死锁发生了

最直接的证据就是应用层报错。MySQL 的 InnoDB 引擎一旦检测到死锁,会自动回滚其中一个事务,并抛出 1213 - Deadlock found when trying to get lock; try restarting transaction。只要在错误日志或异常堆栈里看到这条,基本就能确认是死锁。

另外可以翻一下 InnoDB 的状态信息:

SHOW ENGINE INNODB STATUS;

输出里的 LATEST DETECTED DEADLOCK 段会记下一次死锁的完整现场,包括涉及的事务、被锁住的行、等待的锁类型等等。

2. 分析死锁日志

拿到 InnoDB Status 后,重点看这几项:

  • TRANSACTIONS:列出卷进死锁的两个事务(TRX 1 和 TRX 2)。
  • LOCK WAIT:每个事务在等哪把锁、锁的类型(S 锁 / X 锁)。
  • HOLDS THE LOCK(S):每个事务当前握着哪些锁。
  • SQL STATEMENT:触发死锁的那条 SQL。

把两个事务的「握着什么」和「等着什么」一排,就能看清那个环:事务 A 握着行 X、等行 Y;事务 B 握着行 Y、等行 X——典型的互相等待。

3. 常见原因与快速修复

原因一:加锁顺序不一致

这最容易引发死锁。要是业务代码里有时先改 A 表再改 B 表,有时反过来,交叉等待就出现了。修复办法:把所有操作对同一组表的加锁顺序统一,全局保持一致。

原因二:索引缺失导致全表扫描上锁

WHERE 条件没命中索引时,InnoDB 可能要给大量行甚至整张表加锁,冲突概率一下子就上去了。修复办法:给高频查询条件补上合适的索引,把锁的范围压下去。

原因三:大事务持有锁时间过长

一个事务里塞了太多事(多次查询 + 更新 + 外部调用),锁就被长时间占着。修复办法:把大事务拆小,减少单个事务里的操作量;别在事务里做网络调用或耗时计算。

原因四:外键级联操作触发隐式锁

外键约束里的 CASCADE / SET NULL 会隐式去拿子表的锁,可能跟别的事务撞上。修复办法:先想清楚到底需不需要这个外键,很多高并发场景会选择在应用层自己保证一致性。

4. 预防措施(长期方案)

  • 开慢查询日志 + 死锁监控:把 innodb_print_all_deadlocks = ON 设上,所有死锁都会写进错误日志,后面分析趋势方便得多。
  • 合理设置锁等待超时innodb_lock_wait_timeout 默认 50 秒,可以按业务调短一些(比如 5–10 秒),让等待方更快失败重试。
  • 用乐观锁替代悲观锁:读多写少的场景,可以试试用版本号(version 字段)做乐观并发控制,从根上绕开锁竞争。
  • 定期审查 SQL 与索引:死锁往往只是 SQL 写法或索引设计问题的表象,定期做 SQL 审计、跑跑 EXPLAIN,能把发生概率明显压下来。

5. 应急处理流程总结

  1. 收到告警或报错 → 先确认是不是 Deadlock 异常。
  2. 执行 SHOW ENGINE INNODB STATUS 拿死锁详情。
  3. 定位涉及的表和 SQL → 分析加锁顺序和索引情况。
  4. 临时缓解:重启受影响的事务,或者短暂限流。
  5. 根治:改代码统一加锁顺序、补索引、拆分大事务。

死锁并不是什么搞不定的难题。排查思路和预防手段理顺之后,就能从到处救火变成提前防住。如果这篇文章帮你解决了问题,欢迎到 fenij.com 留言聊聊你的经历。