在真实业务中,你遇到的大多数 mysql 性能问题、死锁问题,几乎都与“锁”有关。但很多工程师对锁的理解停留在碎片层面:
知道“行锁”“间隙锁”“next-key-lock”,但不知道 sql 是如何触发这些锁的、锁到底锁在哪里、为什么会锁这么多。
这一篇文章,我会用工程化思维,带你一次性理解 innodb 锁机制的全景图。
看完以后,你将具备:
- ✔ 能看懂锁的真实作用
- ✔ 能从 sql 推断出锁范围
- ✔ 能解释死锁发生原因
- ✔ 能在面试中条理清晰地讲出锁机制
一、为什么 innodb 的锁机制这么复杂?
mysql 采用 mvcc + 锁 实现事务隔离,其中最关键的隔离级别是:
- rc:读已提交
- rr:可重复读(默认)
rr 是企业最常见的隔离级别,它要解决“幻读”问题。
于是有了三个锁:
- 记录锁(record lock)
- 间隙锁(gap lock)
- next-key lock(record + gap)
所有复杂问题都来自这个组合。
二、innodb 的三种核心锁(理解它们的作用和触发条件)
record lock:真实行上的锁
- 锁的对象:一条真实存在的记录
- 触发场景:精确命中唯一索引
例如:
select * from user where id = 10 for update;
只锁 (10] —— 单条记录。
特点:不会锁间隙,因此不会阻止插入。
gap lock:只锁“间隙”,不锁数据
作用:阻止“间隙内插入新数据”,防止幻读。
例如:
索引中已有值:
10 --- 20 --- 30
sql:
select * from user where age > 20 for update;
gap lock 会锁住:
(20, 30) (30, +∞)
重点:gap lock 不锁记录,只锁区间。
next-key lock:record + gap 的组合锁
rr 下范围查询的默认锁模式:
(prev_key, record_key]
例如:(假设索引有 10、20、30)
select * from t where age between 15 and 25 for update;
锁住的区间:
(10,20] (20,30]
作用:
- ✔ 锁住命中的记录
- ✔ 锁住记录前的 gap → 阻止插入
这就是为什么 rr 隔离级别能规避幻读。
三、锁到底由哪些 sql 触发?
“ sql → 锁类型” 映射表:
| sql 场景 | 索引情况 | 锁类型 | 原因 |
|---|---|---|---|
| where id = ?(唯一键) | 精确命中 | record lock | 不需要锁 gap |
| where id = ?(普通索引) | 精确匹配,但非唯一 | next-key lock | 防止幻读 |
| where age > ? / < ? | 范围扫描 | next-key lock | 必须锁 gap |
| between 范围查询 | 范围扫描 | next-key lock | 防止插入 |
| 无索引过滤 | 全表扫描 | 大量 record lock | 每条记录都会被锁 |
| like '%abc' | 无法走索引 | 表锁风险 | 全表扫描 |
一句话总结:
- 能精确锁住记录 → record lock
- 需要范围扫描 → next-key lock
- 范围扫描一定会锁 gap → gap lock
四、锁具体加在什么区间?
假设索引中有如下值:
10 ---- 20 ---- 30 ---- 40
来看不同 sql 加的锁👇
where id = 20 for update
锁:
(10, 20]
但如果字段是主键/唯一键,会优化成:
[20]
where id > 20 for update
锁:
(20,30) (30,40) (40,+∞)
where id between 15 and 35 for update
锁:
(10,20] (20,30] (30,40]
无索引条件
select * from user where name='xxx' for update;
锁住所有记录:
[10], [20], [30], [40]
→ 大量锁冲突发生的根源。
五、总结
- innodb 的锁永远基于索引。
- 无法精确匹配记录,就会使用 next-key lock。
- 范围查询一定会带 gap 锁。
掌握这三点后,死锁、锁等待、幻读问题都能一眼看穿。
以上为个人经验,希望能给大家一个参考,也希望大家多多支持代码网。
发表评论