SQL 如何处理死锁(Deadlock found when trying to get lock)

7次阅读

Deadlock found when trying to get lock 是 mysql 检测到事务间循环等待锁而主动回滚牺牲事务的正常现象;需通过 SHOW ENGINE INNODB STATUS 查 LATEST DETECTED DEADLOCK 确认,应用层须捕获错误码 1213 并重试。

SQL 如何处理死锁(Deadlock found when trying to get lock)

MySQL 报错 Deadlock found when trying to get lock 是什么情况

这不是数据库坏了,而是两个或多个事务互相持有对方需要的锁,又都在等对方释放,卡住了。MySQL 检测到后会主动杀掉其中一个事务(通常是代价小的那个),让它报这个错,另一个继续执行。

关键点:死锁是正常现象,不能完全避免,但可以大幅降低发生概率;报错的是被选为“牺牲者”的那个事务,它需要重试。

怎么复现和确认是不是真死锁

别靠猜。先查最近的死锁日志:

SHOW ENGINE INNODB STATUSG

重点看 LATEST DETECTED DEADLOCK 区域,里面会明确写出两个事务各自执行了哪条 SQL、持有哪些锁、等待什么锁——这是唯一可信的依据。

常见误判场景:

  • 误把 Lock wait timeout exceeded 当成死锁(其实是单个事务等锁超时,没检测到循环等待)
  • 应用层没捕获 Deadlock found when trying to get lock 错误码(MySQL 返回码是 1213),导致重试逻辑缺失
  • 事务里混用了 select ... for UPDATE 和普通 SELECT,但没加 WHERE 条件,导致锁住整张表

减少死锁的核心操作顺序规则

绝大多数可预防的死锁,根源是事务以不同顺序访问相同资源。解决思路很直接:让所有事务按同一顺序访问行、索引、表。

实操建议:

  • UPDATE/delete 语句必须走索引,且尽量用主键或唯一索引;避免 WHERE status = ? 这类无索引扫描,否则可能锁住大量无关行
  • 批量更新时,先 SELECT id FROM ... ORDER BY id 拿到有序主键列表,再按顺序逐条 UPDATE,不要用 IN (unsorted_ids)
  • 同一个业务逻辑里,如果要更新多张表,固定先后顺序(比如总是先 UPDATE ordersUPDATE order_items
  • 事务粒度尽量短,避免在事务里做 http 调用、文件读写等耗时操作

应用层必须做的错误处理

MySQL 主动抛出死锁后,事务已回滚,连接仍可用。不处理就等于丢数据。

正确做法:

  • 捕获数据库异常中 errno == 1213 或错误信息含 Deadlock found when trying to get lock
  • 最多重试 2–3 次,每次加随机毫秒级延迟(防雪崩),超过则放弃并记录告警
  • 避免在重试逻辑里重新生成业务 ID 或调用幂等性弱的外部接口(比如重复扣款)
  • 如果是高并发写同一行(如库存扣减),考虑改用 UPDATE table SET stock = stock - 1 WHERE id = ? AND stock >= 1 + 影响行数判断,比事务更轻量

真正麻烦的不是死锁本身,而是事务里夹杂了非数据库操作、或者重试时没校验业务前置条件是否已变——这些不会出现在 SHOW ENGINE INNODB STATUS 里,但会让重试变成灾难。

text=ZqhQzanResources