锁分类全解
面试官问:"MySQL InnoDB 有哪些锁类型?"
小陈说:"有行锁和表锁。"
面试官追问:"还有呢?"
小陈:"...间隙锁?"
面试官继续追问:"间隙锁和临键锁有什么区别?意向锁是什么?"
小陈开始混乱了。
这道题,考的是候选人对 InnoDB 锁机制的全面理解。InnoDB 的锁体系非常复杂——按粒度分有行锁和表锁,按类型分有共享锁和排他锁,按作用范围分有记录锁、间隙锁、临键锁,还有意向锁、自增锁等多种辅助锁。能说清楚每种锁的作用和关系的候选人,对 InnoDB 的理解已经相当深入。
一、锁的分类体系 🔴
1.1 按粒度分类
1.2 按类型分类
1.3 按作用对象分类
1.4 ❌ 错误示范
候选人原话:"InnoDB 只有行锁,没有表锁。"
问题诊断:错误。InnoDB 有表锁(MDL 锁、IS/IX 锁),某些场景下还会降级为表锁。
候选人原话 2:"间隙锁和临键锁是一样的。"
问题诊断:临键锁 = 记录锁 + 间隙锁,两者不同。间隙锁只锁间隙,临键锁锁行也锁间隙。
【面试官心理】
这道题我能从基础追问到源码层面。比如我会问:"临键锁锁住的范围是前开后闭区间还是前闭后开?"能答出 [a, b) 的候选人,说明他对 InnoDB 锁算法有深入理解。
二、记录锁(Record Lock)🔴
2.1 记录锁的定义
记录锁锁住的是单条索引记录。
-- 记录锁示例
SELECT * FROM orders WHERE id = 100 FOR UPDATE;
-- 锁住 id = 100 的索引记录
2.2 记录锁的加锁条件
- 主键/唯一索引等值查询:精准锁定一条记录
- 普通索引等值查询 + 查询结果存在:锁定一条记录
- 当前读:任何索引扫描都会加记录锁
2.3 记录锁的兼容性
三、间隙锁(Gap Lock)🟡
3.1 间隙锁的定义
间隙锁锁住的是索引记录之间的空隙,防止其他事务在间隙中插入新记录(防止幻读)。
-- 索引状态:id = {1, 5, 10, 15, 20}
-- 执行:
SELECT * FROM orders WHERE id = 7 FOR UPDATE;
-- 间隙锁锁住区间 (5, 10),即 5 < id < 10 的空隙
-- 其他事务无法在 id 为 6、7、8、9 的位置插入记录
3.2 间隙锁的特点
- 只阻止插入:间隙锁阻止其他事务在间隙中插入,但不阻止其他事务读取该间隙
- 范围是前开后开:间隙锁锁住的是
(a, b) 区间
- 相邻间隙可重叠:两个事务可以在不同间隙加锁
3.3 间隙锁只作用在索引上
-- 如果 id 列没有索引
SELECT * FROM orders WHERE id = 7 FOR UPDATE;
-- InnoDB 会对全表加间隙锁(因为没有索引可锁)
-- 这是灾难性的,应该避免
⚠️
如果 WHERE 条件列没有索引,InnoDB 会锁住全表所有记录的间隙。生产环境中,这种查询会导致严重的锁等待。
四、临键锁(Next-Key Lock)🟡
4.1 临键锁的定义
临键锁是记录锁和间隙锁的组合,锁住的是前开后闭区间 [a, b)。
-- 索引状态:id = {1, 5, 10}
-- 执行:
SELECT * FROM orders WHERE id = 5 FOR UPDATE;
-- 临键锁锁住 [5, 10)
-- 包含:
-- - 记录锁:锁住 id=5 的记录
-- - 间隙锁:锁住 (1, 5) 和 (5, 10) 的间隙
4.2 临键锁的查询场景
-- 等值查询
SELECT * FROM orders WHERE id = 5 FOR UPDATE;
-- 临键锁锁住 [5, 10)
-- 范围查询
SELECT * FROM orders WHERE id >= 5 AND id <= 10 FOR UPDATE;
-- 临键锁锁住多个区间:
-- - [5, 10) 临键锁
-- - [10, 15) 临键锁
-- ...
4.3 临键锁的退化
InnoDB 在某些情况下会退化临键锁为记录锁或间隙锁:
- 唯一索引等值查询:退化临键锁为记录锁
- 查询结果不存在:退化临键锁为间隙锁
- 覆盖索引查询:退化临键锁为记录锁
-- 唯一索引等值查询(退化为记录锁)
SELECT * FROM orders WHERE order_no = 'A001' FOR UPDATE;
-- order_no 是唯一索引,且记录存在
-- 退化为记录锁,只锁 order_no='A001' 这一行
-- 唯一索引查询结果不存在(退化为间隙锁)
SELECT * FROM orders WHERE order_no = 'A999' FOR UPDATE;
-- order_no='A999' 不存在
-- 退化为间隙锁,锁住 (A001, A100) 之间的间隙
五、意向锁(Intention Lock)🟡
5.1 为什么要有意向锁?
意向锁是 InnoDB 为了在表级别快速判断某行是否有锁而设计的。
-- T1: BEGIN;
-- T1: SELECT * FROM orders WHERE id = 1 FOR UPDATE; -- 行锁 X
-- 此时,InnoDB 需要知道"是否有人在等 orders 表的锁"
-- 没有意向锁时:
-- T2: LOCK TABLE orders WRITE; -- 要加表锁
-- T2 需要遍历 orders 表的所有行,看有没有行锁
-- 性能极差
-- 有意向锁时:
-- T1 在表上加 IX(意向排他锁)
-- T2 看到 IX,直接知道"有人在这张表上锁了行"
-- 不需要遍历行,直接阻塞
5.2 意向锁的类型
5.3 意向锁的兼容性矩阵
六、生产避坑 🟡
6.1 无索引查询导致锁全表
-- orders 表有 1000 万行,status 列没有索引
-- 慢查询 + 当前读 = 锁全表
UPDATE orders SET status = 2 WHERE status = 1;
-- status 没有索引,MySQL 选择全表扫描
-- 对扫描到的每一行加临键锁
-- 1000 万行全部被锁,其他事务无法写入
解决方案:在 WHERE 条件列上建立索引。
6.2 范围查询的锁扩大
-- 索引状态:id = {1, 3, 5, 7, 9}
-- 范围查询
SELECT * FROM orders WHERE id BETWEEN 2 AND 6 FOR UPDATE;
-- 临键锁锁住多个区间:
-- - [1, 3) 临键锁
-- - [3, 5) 临键锁
-- - [5, 7) 临键锁
-- (6, 7) 间隙锁
-- 甚至可能锁住 (7, 9) 的间隙
范围查询的临键锁可能覆盖超出预期的区间,导致更大范围的锁等待。
【面试官心理】
这道题我能从基础追问到生产事故。比如我会问:"你线上遇到过死锁吗?怎么排查的?"能说出具体排查过程(SHOW ENGINE INNODB STATUS)和解决方案的候选人,说明他有丰富的实战经验。