InnoDB行锁
InnoDB行锁
InnoDB行锁是通过对索引数据页上的记录加锁实现的,MySQL InnoDB支持三种行锁定方式:
-
记录锁(Record Lock):属于单个行记录上的锁。
-
间隙锁(Gap Lock):锁定一个范围,不包括记录本身。
-
临键锁(Next-Key Lock):Record Lock+Gap Lock,锁定一个范围,包含记录本身,主要目的是为了解决幻读问题(MySQL 事务部分提到过)。记录锁只能锁住已经存在的记录,为了避免插入新记录,需要依赖间隙锁。
-
锁,从来不是为了现在。它是为了控制未来可能发生的变化。
Record Lock(记录锁)
最基础的一种。
它锁的是某一条具体的索引记录。注意,是“索引记录”,不是数据行本身。InnoDB是基于索引实现行锁的,如果没有走索引,事情就会变得危险(可能升级为锁全表)。
例子:
1 | SELECT * FROM user WHERE id = 10 FOR UPDATE; |
如果 id 是主键索引,就会对 id=10 这一条索引记录加记录锁。
特点:
- 精准锁定单条索引记录
- 不锁间隙
- 主要用于等值查询
Gap Lock(间隙锁)
它锁的不是已有数据,而是两条记录之间的“空隙”。
比如索引中有:
1 | 10, 20, 30 |
对范围 (10,20) 加 gap lock,就锁的是这个区间里“还不存在”的值。
作用:
- 防止其他事务在这个区间内插入新数据
- 主要用于解决幻读(Phantom Read)
例如:
1 | SELECT * FROM user WHERE id > 10 AND id < 20 FOR UPDATE; |
在RR(可重复读)隔离级别下,会对区间加gap lock。
Next-Key Lock(临键锁)
Next-Key Lock = Record Lock + Gap Lock
锁住:
- 当前记录
- 当前记录前面的间隙
它锁的是一个左开右闭区间:
1 | (10,20] |
这就是InnoDB在RR隔离级别下默认使用的锁类型。但是,如果操作的索引是唯一索引或主键,InnoDB会对Next-Key Lock进行优化,将其降级为Record Lock,即仅锁住索引本身,而不是范围。
为什么这么设计?
因为:
- 只加记录锁 → 会产生幻读
- 只加 gap lock → 会漏锁已有记录
- 组合起来 → 才能完全避免幻读
这就是InnoDB对“可重复读”的理解方式:通过锁实现,而不是纯 MVCC。
Insert Intention Lock(插入意向锁)
这是一种特殊的 gap lock。
当多个事务要往同一个 gap 插入数据时,它们不会直接冲突,而是先加“插入意向锁”。
作用:
- 表示“我要插入”
- 允许多个插入事务并发等待
- 避免插入之间的死锁
它不是用来阻塞查询的,而是用来协调插入操作的。
总结
如果你是面试回答,可以这样总结:
InnoDB 行锁主要分为:
- Record Lock(记录锁)
- Gap Lock(间隙锁)
- Next-Key Lock(临键锁)
- Insert Intention Lock(插入意向锁)
默认RR隔离级别使用Next-Key Lock防止幻读。
一个关键认知(很多人忽略)
InnoDB的行锁是基于索引实现的。
如果查询没有走索引:
1 | SELECT * FROM user WHERE name = 'abc' FOR UPDATE; |
而name没有索引,那么会锁全表。
行锁瞬间变表锁。世界瞬间变慢。
延伸思考
- 为什么RC隔离级别下会减少gap lock?
- 因为RC允许不可重复读和幻读,所以不需要通过gap lock来防止幻读。
- 在RC隔离级别下,InnoDB会尽量减少gap lock的使用,以提高并发性能。
- MVCC 和 next-key lock 到底是如何配合的?
- 普通SELECT → MVCC
- SELECT FOR UPDATE → Next-Key Lock
- RR = MVCC 保证行一致 + Next-Key Lock 保证范围一致
- 为什么唯一索引的等值查询不会加 gap lock?
- InnoDB只在“未来可能产生新的匹配记录”时才加gap lock。
- 唯一索引等值命中时,未来是封闭的。
