MySQL 锁与死锁:行锁、间隙锁、Next-Key Lock 与排查手册

事务讲到最后,面试官往往会把你从"隔离级别"拉到"线上事故":

  • 为什么卡住?
  • 为什么死锁?
  • 为什么 RR 下会出现间隙锁?

你必须记住的 3 句话(面试直出):

  • InnoDB 的锁本质是:为保证语义,在索引上加锁,不是在"表/行"这个抽象上加锁。
  • RR 下为避免范围内插入导致语义破坏,会出现 Next-Key Lock(记录锁 + 间隙锁)
  • 死锁不是"代码写错了"这么简单,更多是 锁顺序不一致 + 范围条件/索引缺失扩大锁范围

1. 先建立锁的主线:锁的是"索引范围"

常见锁类型(面试够用版):

  • 记录锁(Record Lock):锁住某条索引记录
  • 间隙锁(Gap Lock):锁住索引记录之间的间隙,防止插入
  • Next-Key Lock:记录锁 + 间隙锁,锁住一个"半开区间"的范围

关键结论:

  • 是否走索引,决定锁的粒度
  • 没有合适索引,可能导致更大的范围被锁(甚至看起来像"锁表")。

2. 为什么会有间隙锁:是为了保证 RR 的范围语义

你可以用这个典型场景解释:

  • 事务 A:SELECT ... FOR UPDATE WHERE age BETWEEN 10 AND 20
  • 如果不锁间隙,事务 B 可以插入一条 age=15
  • A 再次执行范围更新/校验时,语义就被破坏(出现"范围内新增")

因此 RR 下,当前读/范围修改可能引入:

  • 间隙锁、Next-Key Lock

更精确的工程口径(避免被追问时说含糊):

  • 间隙锁主要出现在 RR + 当前读/修改 + 范围扫描 这些组合里
  • 如果能被 唯一索引等值命中,很多场景会退化成记录锁(锁范围更小)
  • 一旦是 非唯一索引范围条件索引缺失导致扫描,锁范围就容易膨胀

面试加分表达:

  • 间隙锁不是为了"防幻读这个词",而是为了"防止范围内出现新记录导致语义不一致"

3. 死锁怎么来的:用"锁顺序"解释最稳

死锁的经典结构:

  • T1 持有 A,等待 B
  • T2 持有 B,等待 A

在 InnoDB 里常见触发点:

  • 两个事务更新同一批行,但 更新顺序不同
  • 范围条件导致锁集合不一致(或扩大锁范围)
  • 二级索引更新会涉及主键索引回表锁定(锁链更复杂)

一个工程上最常见的死锁模型:

  • 事务 1:先更新 id=1 再更新 id=2
  • 事务 2:先更新 id=2 再更新 id=1

解决思路通常不是"加大超时",而是:

  • 固定更新顺序(按主键升序)
  • 缩小事务
  • 确保条件走索引,减少锁范围

再补一组更"线上可落地"的降死锁动作(你可以按优先级讲):

  • 固定访问顺序:同一批资源按主键/业务 key 排序后依次更新
  • 把"先查再改"改为"条件更新" :把校验写进 UPDATE ... WHERE ...,减少锁持有时间与往返
  • 避免范围大批量更新:拆批、按主键分页、并控制并发
  • 补齐索引:让条件尽量走到"更小范围"的索引记录,减少 Next-Key 锁范围
  • 只做兜底的重试:对死锁错误做幂等重试,但不把重试当根治

4. 最容易踩坑的 4 个点

  • 坑 1:SELECT ... FOR UPDATE 以为只锁一行

    • 走范围条件/非唯一索引时可能锁范围(Next-Key)。
  • 坑 2:缺失索引导致锁范围扩大

    • 条件无法定位到小范围索引记录,锁住大片区间。
  • 坑 3:在事务里做慢操作

    • 事务越长,持锁越久,冲突越明显。
  • 坑 4:批量更新/分页更新的锁冲突

    • 大事务 + 范围扫描容易放大锁等待与死锁概率。

5. 线上排查手册:锁等待与死锁

5.1 锁等待(接口变慢/超时)

你要快速回答三个问题:

  • 谁在等?
  • 在等谁?
  • 等的锁是什么范围?

常用手段:

  • SHOW PROCESSLIST:看到 Waiting for ... lock
  • SHOW ENGINE INNODB STATUS:看 TRANSACTIONSLATEST DETECTED DEADLOCK
  • performance_schema / sys 库视图:定位阻塞链

落地判断:

  • 如果阻塞链上"持锁者"的 SQL 很慢/事务很长,先把它当"长事务/慢 SQL"处理。

5.2 死锁(InnoDB 会主动回滚一方)

现象:

  • 应用报错:Deadlock found when trying to get lock; try restarting transaction

处理姿势:

  • 第一时间取 SHOW ENGINE INNODB STATUS(死锁信息会被覆盖,别拖)
  • 看清楚:
    • 哪两个事务
    • 各自持有什么锁
    • 各自等待什么锁

你在 status 里需要重点抓的"可还原信息"(面试/实战都加分):

  • 两边事务的 SQL(到底是哪条语句触发)
  • 锁的是哪个索引(主键/二级索引)
  • 是等值还是范围(决定是否可能是 Next-Key)
  • 两个事务的执行顺序(是否锁顺序相反)

工程修复优先级:

  • 固定锁顺序(按主键排序更新)
  • 缩小事务与批次
  • 补索引/改查询条件,缩小锁范围
  • 业务上允许时做幂等重试(只作为兜底,不是根治)

6. 自测清单(你要能顺口讲出来)

  • Q:InnoDB 的锁锁在哪里?

    • A:锁在索引上;是否走索引决定锁粒度与范围。
  • Q:Next-Key Lock 是什么?

    • A:记录锁 + 间隙锁,用于 RR 下的范围当前读/修改,保证范围语义。
  • Q:死锁怎么降低概率?

    • A:固定锁顺序、缩短事务、让条件走索引缩小锁范围,必要时做幂等重试。

7. 30 秒背诵稿

InnoDB 为保证事务语义会在索引上加锁,常见有记录锁、间隙锁和 Next-Key Lock。RR 下范围当前读/修改为了防止范围内插入破坏语义,可能加 Next-Key 锁导致锁范围变大。死锁通常来自锁顺序不一致或索引缺失导致锁集合扩大,线上先用 processlist/innodb status/performance_schema 找到阻塞链和死锁双方,再通过固定更新顺序、缩短事务、补索引来根治。

相关推荐
2401_8877245015 小时前
CSS如何设置文字溢出显示省略号_利用text-overflowellipsis
jvm·数据库·python
m0_7478545215 小时前
golang如何实现应用启动耗时分析_golang应用启动耗时分析实现思路
jvm·数据库·python
雪碧聊技术15 小时前
下午题_试题二
数据库
解救女汉子15 小时前
如何截断SQL小数位数_使用TRUNCATE函数控制精度
jvm·数据库·python
2301_8038756115 小时前
如何用 objectStore.get 根据主键 ID 获取数据库单条数据
jvm·数据库·python
weixin_4585801215 小时前
如何修改AWR保留时间_将默认8天保留期延长至30天的设置
jvm·数据库·python
qq_6543669815 小时前
C#怎么实现OAuth2.0授权_C#如何对接第三方快捷登录【核心】
jvm·数据库·python
justjinji16 小时前
如何用 CSS 变量配合 JS setProperty 实现动态换肤功能
jvm·数据库·python
2301_8038756116 小时前
C#怎么使用TopLevel顶级语句 C#顶级语句怎么写如何省略Main方法简化控制台程序【语法】
jvm·数据库·python
九皇叔叔16 小时前
MySQL 8.0 测试库安装
数据库·mysql