MySQL已产生死锁的解决方法及永久避免方案

作者:技术拾光时间:2026-06-04 08:58:02浏览:176

在MySQL数据库中,死锁的发生可以用一个场景来理解:多个事务相互争夺资源,互不相让,最终导致全部阻塞。MySQL内置的死锁检测机制能够迅速发现这种局面,并主动选择一个代价最小的事务进行回滚,同时抛出死锁异常,使另一个事务得以继续执行。这正是我们经常看到“Deadlock found when tr

在MySQL数据库中,死锁的发生可以用一个场景来理解:多个事务相互争夺资源,互不相让,最终导致全部阻塞。MySQL内置的死锁检测机制能够迅速发现这种局面,并主动选择一个代价最小的事务进行回滚,同时抛出死锁异常,使另一个事务得以继续执行。这正是我们经常看到“Deadlock found when trying to get lock”错误的原因。

本文主要解决两大实际问题:

  1. 死锁发生后如何处理:业务被死锁卡住时,如何快速恢复?
  2. 从根源上预防死锁:如何避免此类“堵车”反复出现?

MySQL已产生死锁的解决方法及永久避免方案

一、已产生死锁:三步快速解决

1. 无需手动杀进程:MySQL 会自动处理

MySQL 默认开启了死锁检测机制。一旦检测到死锁,系统会:

  • 立即回滚代价最小的那个事务
  • 向应用层抛出 Deadlock found when trying to get lock; try restarting transaction 异常
  • 另一个事务不受影响,继续正常执行——死锁瞬间解除

因此,首要认知是:死锁发生后,无需人工干预,MySQL 自身可在秒级解决问题。

2. 查看死锁日志:找到根源(最重要)

虽然 MySQL 已经自动处理了死锁,但了解死锁原因至关重要。定位原因只需一条命令:

-- 查看最近一次死锁的详细信息SHOW ENGINE INNODB STATUS;

在返回结果中找到 LATEST DETECTED DEADLOCK 部分,它会提供:

  • 发生死锁的两个事务
  • 各自执行的 SQL 语句
  • 各自持有的锁以及等待的锁
  • 死锁发生的时间

一句话:这是后续所有调整工作的唯一可靠依据。

3. 临时应急:卡住的事务不自动解除?

极少数情况下,如果手动关闭了死锁检测,事务会一直挂起。此时可通过以下命令手动处理:

-- 1. 查看正在运行的事务,找到卡住的 IDSELECT * FROM INFORMATION_SCHEMA.INNODB_TRX;-- 2. 杀掉卡住的事务KILL 事务ID;

二、永久避免死锁:四个核心方案(必做)

从技术层面讲,死锁无法100%杜绝,但通过规范化的设计和习惯,99%的死锁完全可以避免。

1. 事务保持短小,避免长事务

  • 死锁概率与事务执行时间成正比,这是难以规避的规律
  • 禁止在事务中嵌套网络请求、文件 IO、人工操作或 sleep
  • 基本原则:快进快出,执行完毕立即提交或回滚

典型反面案例:

BEGIN;UPDATE 表 SET ...;-- 这里调用了外部接口,耗时3秒,锁一直持有,极易死锁COMMIT;

3秒的耗时,在数据库世界中已属于“长事务”。

2. 所有表固定访问顺序(最有效)

死锁的本质在于访问顺序不一致。

  • 事务 A:先锁订单 → 再锁库存
  • 事务 B:先锁库存 → 再锁订单

→ 结果就是瞬间死锁。

解决方案:在业务代码层,所有表和行的访问顺序必须统一。例如,所有人都遵循“先操作库存,再操作订单”,永不例外。

3. 给查询条件加索引

没有索引时,行锁会升级为表锁,死锁概率大幅提升。

-- 错误:无索引,会锁全表UPDATE user SET money=100 WHERE name='张三'; -- 正确:name 有索引,只锁单行UPDATE user SET money=100 WHERE name='张三'; 

如果在死锁日志中发现 LOCK_MODE: X, REC_NOT_GAP: nil,基本可判断为无索引导致。

4. 业务代码捕获死锁异常,自动重试

死锁本质上是偶发异常,重试一次即可解决,是最稳妥的兜底措施。

以 Java 为例:

try {    // 执行数据库操作} catch (SQLTransactionRollbackException e) {    if (e.getMessage().contains("Deadlock")) {        // 死锁异常,重试1-2次        retry();    }}

三、快速排查死锁 checklist

  1. 执行 SHOW ENGINE INNODB STATUS; 查看死锁详情
  2. 检查两个事务是否访问表/行顺序相反
  3. 检查更新语句是否没有索引
  4. 检查是否存在长事务
  5. 业务代码是否没有重试机制

总结

  1. 已产生的死锁:MySQL 自动回滚一个事务,无需手动介入
  2. 查找死锁原因:使用 SHOW ENGINE INNODB STATUS;
  3. 根治死锁:固定访问顺序 + 加索引 + 短事务 + 代码重试
  4. 兜底:业务层捕获死锁异常并自动重试,用户几乎无感知