服务端与数据 / 进阶

MySQL 死锁:统一加锁顺序,并重试整个事务

死锁不是数据库失控,而是并发事务形成了循环等待。本文介绍报告分析、索引影响、锁顺序和有限重试。

死锁和普通锁等待不同

事务 A 持有记录 1 等待记录 2,事务 B 持有记录 2 等待记录 1,就形成循环。InnoDB 会选择一个事务回滚以打破循环。应用收到 1213 错误后可以重试,但首先要确认事务本身是幂等且重试范围完整。

EXAMPLE / 01MySQL
SHOW ENGINE INNODB STATUS;

-- 生产环境也可启用 innodb_print_all_deadlocks
-- 将完整死锁信息写入错误日志

固定访问顺序并缩小扫描范围

所有转账都按较小 account_id 到较大 account_id 加锁,就不会因调用方向不同而反向持锁。缺少合适索引时,UPDATE 还可能扫描并锁住更多记录,因此执行计划也是排查锁冲突的一部分。

对多行资源按稳定主键排序后再加锁;让 WHERE 条件命中索引,减少不必要的记录锁和间隙锁;事务内不做用户交互、HTTP 调用和长计算;保持各业务路径的表访问顺序一致。

有限次数重试整个事务

持续死锁通常表示访问路径有设计问题。记录冲突事务、SQL 指纹和重试次数,超过上限后失败并告警。

EXAMPLE / 03MySQL
for (let attempt = 1; attempt <= 3; attempt++) {
  try {
    return await runTransaction();
  } catch (err) {
    if (err.errno !== 1213 || attempt === 3) throw err;
    await wait(20 * attempt + Math.random() * 30);
  }
}