首页/数据/数据库速查/死锁预防的工程手段
性能与调优57 条

死锁预防的工程手段

预防死锁四招:按固定顺序访问表/行、事务短小、更新前先 SELECT ... FOR UPDATE 定序、热点行拆散(如账户余额分桶)。

数据库速查数据更新于 2026-10-13

死锁的产生与应对思路

InnoDB 中两个及以上事务互相持有对方需要的锁、又不肯释放,就形成死锁,数据库检测到后会回滚代价较小的一方并报 1213 错误。工程上更现实的目标是减少发生,而不是彻底消除:统一加锁顺序、缩小事务范围、把热点行拆散,再配合应用层的有限次退避重试,就能把死锁从事故变成可预期的小概率事件。

基本信息

适用平台InnoDB(MySQL 5.7 / 8.0)
错误码1213 Deadlock found when trying to get lock
开启日志innodb_print_all_deadlocks = ON
诊断入口SHOW ENGINE INNODB STATUS 的 LATEST DETECTED DEADLOCK
重试原则有限次数 + 随机退避,避免重试风暴

典型使用场景

  • 转账、扣减库存等成对更新两张表或两行数据的业务
  • 批量任务与在线事务同时改同一批记录
  • 订单状态流转:多状态字段被不同流程交叉更新
  • 唯一键冲突与插入意向锁交织导致的死锁排查
  • 长事务持有锁期间被短事务反复碰撞

定序加锁示例

定序加锁示例
-- 反例:两个事务按不同顺序更新两行,极易互等
-- 正例:统一按 id 升序加锁,破坏循环等待
BEGIN;
SELECT * FROM appdb.accounts WHERE id = 1 FOR UPDATE;
SELECT * FROM appdb.accounts WHERE id = 2 FOR UPDATE;
UPDATE appdb.accounts SET balance = balance - 100 WHERE id = 1;
UPDATE appdb.accounts SET balance = balance + 100 WHERE id = 2;
COMMIT;

定位最近一次死锁

定位最近一次死锁
-- 打开死锁日志记录(8.0 支持动态开启)
SET GLOBAL innodb_print_all_deadlocks = ON;
-- 查看最近一次死锁的两个事务与各自持有的锁
SHOW ENGINE INNODB STATUS\G
-- 8.0 可从 performance_schema 查看当前等待关系
SELECT * FROM sys.innodb_lock_waits\G
-- 查看未提交事务(长事务是死锁的放大器)
SELECT trx_id, trx_state, trx_started, trx_mysql_thread_id FROM information_schema.innodb_trx;

常见死锁模式

模式成因规避
加锁顺序不一致两事务更新同两行的顺序相反统一按主键升序处理
间隙锁互等范围查询加临键锁后交叉更新缩小范围条件或改用等值匹配
唯一键冲突并发插入同一唯一键先查后插、捕获重复键错误重试
大事务持锁时间过长事务内混入 RPC、循环更新拆小事务,先计算后写库
热点行集中更新计数器、库存单行被高频更新分桶或改异步合并写
出现死锁说明两个事务在锁顺序上有冲突,这是业务模型问题不是数据库故障:先定位是哪两条 SQL 交叉,再决定是调顺序、拆事务还是改数据模型。
重试必须有上限和退避:死锁风暴期间无限制重试会把数据库压力放大数倍,形成自我加剧的恶性循环。

易错点

  • 两个事务按不同顺序更新同样的行:必现循环等待,务必在代码层统一处理顺序(如按 ID 排序后批量处理)
  • 重试不设上限也无退避:死锁频繁时重试放大压力,把局部问题变成全局故障
  • 大事务里夹带外部调用或循环更新:持锁时间被拉长,与其他事务的碰撞窗口成倍扩大
  • 把业务层锁与数据库锁混用:两层锁顺序无法统一,死锁排查难度急剧上升

数据来源与参考

  • MySQL 8.0 Reference Manual: Deadlocks

相关工具

​‌‌​​‌​​​‌‌​‌‌‌‌​‌‌​​​‌‌​‌‌‌​​‌‌​​‌​‌‌‌​​‌‌‌​​​‌​‌‌​‌​​‌​‌‌‌​​​‌​‌‌​‌​​‌​‌‌​​​‌‌​‌‌‌​‌​​​‌‌​‌‌‌‌​​‌​‌‌‌​​‌‌​​​‌‌​‌‌​‌‌‌‌​‌‌​‌‌​‌