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

相关推荐
皙然2 小时前
Redis 持久化机制超详细详解(RDB+AOF 双方案 + 生产实战)
数据库·redis·bootstrap
Magic--2 小时前
进程间通信(IPC):原理、场景与选型
java·服务器·数据库
xhuiting2 小时前
MySQL专题总结(三)—— 补充篇
数据库·mysql
智象科技2 小时前
告警自动化赋能运维:意义与价值解析
网络·数据库·人工智能·自动化·告警·一体化运维·ai运维
源远流长jerry2 小时前
在云环境中部署 NFV:OpenStack 讲解
数据库·openstack
※DX3906※2 小时前
SpringBoot之旅4: MyBatis 操作数据库(进阶) 动态SQL+MyBatis-Plus实战,从入门到熟练,再也不踩绑定异常、SQL拼接坑
java·数据库·spring boot·spring·java-ee·maven·mybatis
J超会运3 小时前
OpenEuler系统MySQL备份恢复全攻略
mysql·mysql备份
Carino_U3 小时前
全面理解mysql架构
mysql·adb·架构
小的~~3 小时前
使用StreamLoad向Doris-4.0.3版本的聚合表导数据超时问题
运维·服务器·数据库