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 找到阻塞链和死锁双方,再通过固定更新顺序、缩短事务、补索引来根治。

相关推荐
考虑考虑6 小时前
数据库中的EXISTS
运维·数据库·后端
Wang's Blog6 小时前
Java框架快速入门: Spring Security+OAuth2之数据库和实体类的RBAC改造
java·数据库·spring
梦想平凡7 小时前
百游棋牌源代码开发搭建教程(十):隔离部署、备份恢复与双端验收
java·前端·javascript·数据库·源代码管理
xcLeigh7 小时前
让大模型长出手脚,自己写SQL查数据库(Function Calling初探)
数据库·人工智能·sql·时序数据库·timechoai
yume_sibai8 小时前
06-Rust Web 开发实战(Axum 框架 + 数据库 + JWT 认证 + 中间件 + 部署)
前端·数据库·rust
AugustRed9 小时前
Neo4j 图数据库原理 + 应用场景简单介绍
数据库·neo4j
Gl�ria10 小时前
Redis 高可用架构对比
数据库·redis
IpdataCloud10 小时前
高可用的IP数据接口平台有哪些?从可用性、P99延迟到离线部署的评估框架(含代码)
数据库·tcp/ip·ip
呆萌很10 小时前
MySQL 数据库和表的管理操作(命令行)
数据库·mysql
毕业设计70311 小时前
(免费领源码)基于Python的博物馆研学活动管理系统的设计与实现22724- java、PHP、python、C#、小程序、大数据、单片机、网络工程等)
python·mysql·随机森林·pycharm·django·flask·推荐算法