【大白话说Java面试题 第86题】【Mysql篇】第16题:MySQL 中锁的种类与行锁实现原理?
📌PDF:大白话说Java面试题 — 03-Mysql篇
第16题:MySQL 中锁的种类与行锁实现原理
📚回答:
- 核心考点:
大厂面试要求深入理解锁的分类体系、InnoDB行锁的底层实现、各种锁的加锁时机与兼容性,以及行锁与隔离级别的关联。面试官常追问:“Record Lock、Gap Lock、Next-Key Lock有什么区别?”、“意向锁的作用是什么?”、“行锁是怎么在索引上实现的?”
1. 锁的完整分类体系
MySQL的锁可以从三个维度分类:加锁思想、锁粒度、锁类型。
| 分类维度 | 类型 | 说明 |
|---|---|---|
| 加锁思想 | 乐观锁 / 悲观锁 | 逻辑上的并发控制策略,非数据库内置锁 |
| 锁粒度 | 表锁 / 页锁 / 行锁 | 锁的范围大小 |
| 锁类型(InnoDB行锁) | Record Lock / Gap Lock / Next-Key Lock / Insert Intention Lock | InnoDB行锁的具体实现 |
| 表级辅助锁 | 意向锁(IS/IX) | 用于表锁与行锁的协调 |
2. 乐观锁 vs 悲观锁(并发控制思想)
| 对比 | 乐观锁 | 悲观锁 |
|---|---|---|
| 核心思想 | 假设冲突少,先操作后检测 | 假设冲突多,先加锁后操作 |
| 实现方式 | 版本号(version)、CAS | SELECT FOR UPDATE、LOCK IN SHARE MODE |
| 是否数据库内置 | 否(应用层实现) | 否(依赖于数据库提供的锁) |
| 适用场景 | 读多写少 | 写多读少 |
乐观锁示例:
-- 使用version字段实现乐观锁UPDATEproductsSETstock=stock-1,version=version+1WHEREid=1ANDversion=5;-- 检查affected_rows,为0则重试悲观锁示例:
STARTTRANSACTION;SELECT*FROMproductsWHEREid=1FORUPDATE;-- 加排他锁UPDATEproductsSETstock=stock-1WHEREid=1;COMMIT;3. 按锁粒度分类
| 锁粒度 | 支持引擎 | 特点 | 性能 |
|---|---|---|---|
| 表锁 | MyISAM、InnoDB(DDL/意向锁) | 锁定整表,简单但并发差 | 低 |
| 页锁 | BDB(已废弃) | 锁定数据页,介于表锁和行锁之间 | 中 |
| 行锁 | InnoDB | 锁定索引记录,并发高 | 高 |
InnoDB表锁场景:
- DDL操作(如
ALTER TABLE)会加表锁 - 事务执行
LOCK TABLES ... WRITE显式加表锁 - 意向锁(IS/IX)是表级锁,用于快速判断表锁兼容性
4. InnoDB 行锁的四种类型(核心)
4.1 记录锁(Record Lock)
- 定义:锁定索引记录(不是行本身),防止其他事务修改或删除该记录
- 加锁对象:索引项(没有索引时自动使用隐藏聚簇索引)
- SQL示例:
SELECT*FROMusersWHEREid=1FORUPDATE;-- 锁定id=1的索引记录4.2 间隙锁(Gap Lock)
- 定义:锁定索引记录之间的间隙(开区间),不包括记录本身
- 作用:防止其他事务在间隙中插入新记录,避免幻读
- 生效条件:RR隔离级别及以上
- SQL示例:
-- 假设id值:1, 3, 5, 7, 9SELECT*FROMusersWHEREidBETWEEN3AND5FORUPDATE;-- 锁定间隙:(1,3)、(3,5)、(5,7)4.3 Next-Key Lock
- 定义:Record Lock + Gap Lock,锁定一个左开右闭的区间
- 计算公式:Next-Key Lock = (前一个值, 当前值] 的间隙锁 + 当前值的记录锁
- 作用:InnoDB在RR级别下的默认行锁算法,同时防止幻读和保证当前读一致性
- SQL示例:
-- 假设id值:1, 3, 5, 7, 9SELECT*FROMusersWHEREid=5FORUPDATE;-- Next-Key Lock锁定范围:(3,5] 和 (5,7]-- 即锁住id=5的记录,以及(3,5)和(5,7)的间隙4.4 插入意向锁(Insert Intention Lock)
- 定义:一种特殊的间隙锁,表示事务意图在某个间隙中插入数据
- 特性:多个事务可以同时在同一间隙中持有插入意向锁,只要插入的位置不冲突(不同索引值)
- 作用:提高插入并发性,避免间隙锁完全阻塞所有插入
-- 事务A锁定间隙(3,5)SELECT*FROMusersWHEREid=4FORUPDATE;-- 加间隙锁(3,5)-- 事务B在间隙(3,5)中插入id=4.5INSERTINTOusersVALUES(4.5,'xxx');-- 阻塞(与间隙锁冲突)-- 事务C在间隙(3,5)中插入id=4.8INSERTINTOusersVALUES(4.8,'xxx');-- 也阻塞(间隙锁阻塞所有插入)5. 行锁的加锁规则与退化
唯一索引等值查询:
| 条件 | 加锁类型 | 示例(id主键:1,3,5,7,9) |
|---|---|---|
| 命中 | Record Lock | WHERE id=5→ 锁5(Next-Key锁退化) |
| 未命中 | Gap Lock | WHERE id=4→ 锁(3,5)间隙 |
普通索引等值查询:
| 条件 | 加锁类型 | 示例(age普通索引:1,3,5,7,9) |
|---|---|---|
| 命中 | Next-Key Lock | WHERE age=5→ 锁(3,5]和(5,7) |
| 未命中 | Gap Lock | WHERE age=4→ 锁(3,5)间隙 |
范围查询:
-- 唯一索引范围SELECT*FROMusersWHEREid>5FORUPDATE;-- 锁:所有id>5的Record Lock + 后续Gap Lock到无穷大-- 普通索引范围SELECT*FROMusersWHEREage>5FORUPDATE;-- 锁:所有age>5的Next-Key Lock6. 意向锁(Intention Lock)详解
6.1 什么是意向锁
意向锁是表级锁,表示事务意图对表中的某些行加锁。
| 锁类型 | 含义 | 加锁时机 |
|---|---|---|
| 意向共享锁(IS) | 事务意图对某些行加共享锁(S锁) | SELECT ... LOCK IN SHARE MODE |
| 意向排他锁(IX) | 事务意图对某些行加排他锁(X锁) | SELECT ... FOR UPDATE、UPDATE、DELETE |
6.2 意向锁的作用
避免表锁与行锁的冲突检测遍历:
- 事务A要对整表加
LOCK TABLES ... WRITE(表级X锁) - 如果没有意向锁,需要遍历所有行检查是否有行锁 → O(n)复杂度
- 有意向锁时,只需检查表上的意向锁 → O(1)复杂度
6.3 意向锁兼容性矩阵
| 锁类型 | IS | IX | S(表级) | X(表级) |
|---|---|---|---|---|
| IS | ✅ | ✅ | ✅ | ❌ |
| IX | ✅ | ✅ | ❌ | ❌ |
| S(表级) | ✅ | ❌ | ✅ | ❌ |
| X(表级) | ❌ | ❌ | ❌ | ❌ |
关键规律:
- 意向锁之间相互兼容(IS与IS、IS与IX、IX与IX都兼容)
- 意向锁与表级共享锁(S)部分兼容(IS兼容,IX不兼容)
- 意向锁与表级排他锁(X)不兼容
7. 行锁与隔离级别的关系
| 隔离级别 | 是否使用Gap Lock | 幻读风险 | 说明 |
|---|---|---|---|
| READ UNCOMMITTED | ❌ | 有 | 几乎不加锁 |
| READ COMMITTED | ❌ | 有 | 只有Record Lock,无Gap Lock |
| REPEATABLE READ | ✅ | 无 | InnoDB默认,使用Next-Key Lock |
| SERIALIZABLE | ✅ | 无 | 所有SELECT隐式加锁 |
RC vs RR的行锁差异:
-- RC级别:只有Record Lock-- RR级别:Next-Key Lock(Record Lock + Gap Lock)-- 示例:假设id值:1, 3, 5, 7, 9SELECT*FROMusersWHEREid=5FORUPDATE;-- RC级别:只锁id=5的记录(其他事务可插入4、6)-- RR级别:锁(3,5]和(5,7)(其他事务不可插入4、6)8. 行锁的实现原理(源码层面)
8.1 行锁加在索引上
InnoDB的行锁本质是索引项锁:
- 通过主键索引查询 → 锁聚簇索引记录
- 通过二级索引查询 → 先锁二级索引记录,再锁聚簇索引记录
为什么必须是索引?
- 行锁通过索引定位记录
- 没有索引条件时,InnoDB会锁全表(所有聚簇索引记录)
8.2 两阶段锁协议(2PL)
- 加锁阶段:事务开始到提交前,可以随时加锁
- 解锁阶段:事务提交或回滚时,统一释放所有锁
- 无中途解锁:InnoDB不支持
UNLOCK操作
8.3 锁的内存结构
每个锁在内存中对应一个lock_t结构,包含:
trx_id:持有锁的事务IDtype_mode:锁类型(S/X/IS/IX/GAP等)index:锁定的索引bitmap:行记录位图(一个锁可锁定多条记录)
9. 死锁检测与处理
9.1 死锁产生条件
四个必要条件:互斥、持有并等待、不可剥夺、循环等待。
9.2 典型死锁场景
-- 事务ASTARTTRANSACTION;UPDATEusersSETname='A'WHEREid=1;-- 锁id=1UPDATEusersSETname='A'WHEREid=2;-- 等待事务B释放id=2-- 事务BSTARTTRANSACTION;UPDATEusersSETname='B'WHEREid=2;-- 锁id=2UPDATEusersSETname='B'WHEREid=1;-- 等待事务A释放id=1-- 死锁形成9.3 InnoDB死锁处理机制
- 死锁检测:维护事务等待图(
trx_t和lock_t),检测到循环等待时立即处理 - 死锁回滚:回滚代价较小的事务(修改行数少的)
- 参数控制:
innodb_deadlock_detect=ON(默认开启)innodb_lock_wait_timeout=50(死锁检测超时,默认50秒)
查看死锁信息:
SHOWENGINEINNODBSTATUS;-- 查看最近死锁SELECT*FROMinformation_schema.INNODB_TRX;-- 查看当前事务SELECT*FROMinformation_schema.INNODB_LOCKS;-- 查看当前锁10. 总结对比表
| 锁类型 | 粒度 | 作用 | 是否阻塞插入 | RR级别默认 |
|---|---|---|---|---|
| Record Lock | 行(索引记录) | 锁定单条记录 | 否 | ✅ |
| Gap Lock | 间隙 | 锁定索引间隙,防幻读 | ✅ | ✅ |
| Next-Key Lock | 行+间隙 | 锁定记录及前间隙 | ✅ | ✅(默认) |
| Insert Intention Lock | 间隙 | 表示意图插入 | 特殊(位置不冲突时兼容) | ✅ |
| 意向锁(IS/IX) | 表 | 协调表锁与行锁 | 否 | ✅ |
💡面试官想要的满分总结:
"InnoDB的锁体系从三个维度理解:
一、按加锁思想:乐观锁(版本号/CAS,应用层实现)和悲观锁(数据库行锁/表锁)。
二、按锁粒度:表锁(MyISAM/DDL)、行锁(InnoDB核心)、页锁(已废弃BDB)。
三、InnoDB行锁四种类型:
- Record Lock:锁定索引记录(行锁的基础)
- Gap Lock:锁定索引间隙,防幻读(RR级别生效)
- Next-Key Lock:Record Lock + Gap Lock,RR级别默认算法
- Insert Intention Lock:特殊的间隙锁,提高插入并发性
**意向锁(IS/IX)**是表级锁,用于快速判断表锁兼容性,避免遍历所有行检查行锁。
实现原理:
- 行锁实质是索引项锁,通过索引定位记录
- 遵循两阶段锁协议(2PL):事务结束时统一释放锁
- 死锁检测通过等待图实现,回滚代价较小的事务
一句话:InnoDB行锁 = 基于索引的记录锁 + RR级别下的间隙锁(防幻读),通过意向锁快速协调表锁与行锁,通过死锁检测自动处理循环等待。"
觉得对您有帮助,麻烦点点关注啦,您的关注是我创作的最大动力~ 🎯
