锁分类全解

面试官问:"MySQL InnoDB 有哪些锁类型?"

小陈说:"有行锁和表锁。"

面试官追问:"还有呢?"

小陈:"...间隙锁?"

面试官继续追问:"间隙锁和临键锁有什么区别?意向锁是什么?"

小陈开始混乱了。

这道题,考的是候选人对 InnoDB 锁机制的全面理解。InnoDB 的锁体系非常复杂——按粒度分有行锁和表锁,按类型分有共享锁和排他锁,按作用范围分有记录锁、间隙锁、临键锁,还有意向锁、自增锁等多种辅助锁。能说清楚每种锁的作用和关系的候选人,对 InnoDB 的理解已经相当深入。

一、锁的分类体系 🔴

1.1 按粒度分类

锁类型作用范围特点
行锁(Record Lock)单行记录粒度最小,并发最高,开销最大
间隙锁(Gap Lock)索引记录之间的间隙防止幻读
临键锁(Next-Key Lock)行锁 + 间隙锁的组合InnoDB 默认的锁算法
表锁(Table Lock)整张表粒度最大,开销最小,并发最低

1.2 按类型分类

锁类型简称作用兼容性
共享锁(Shared Lock)S 锁读取数据S 锁和 S 锁兼容,S 锁和 X 锁互斥
排他锁(Exclusive Lock)X 锁修改数据X 锁和任何锁都互斥

1.3 按作用对象分类

锁类型作用对象说明
记录锁(Record Lock)单条索引记录最基础的锁,锁住单行
间隙锁(Gap Lock)索引间隙锁住记录之间的空隙
临键锁(Next-Key Lock)记录 + 间隙锁住前一个记录到当前记录的范围
意向锁(Intention Lock)表示事务想在某行加锁
自增锁(Auto-Inc Lock)AUTO_INCREMENT 列的锁

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 记录锁的加锁条件

  1. 主键/唯一索引等值查询:精准锁定一条记录
  2. 普通索引等值查询 + 查询结果存在:锁定一条记录
  3. 当前读:任何索引扫描都会加记录锁

2.3 记录锁的兼容性

锁类型S 锁X 锁
S 锁✅ 兼容❌ 互斥
X 锁❌ 互斥❌ 互斥

三、间隙锁(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 间隙锁的特点

  1. 只阻止插入:间隙锁阻止其他事务在间隙中插入,但不阻止其他事务读取该间隙
  2. 范围是前开后开:间隙锁锁住的是 (a, b) 区间
  3. 相邻间隙可重叠:两个事务可以在不同间隙加锁

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 在某些情况下会退化临键锁为记录锁或间隙锁:

  1. 唯一索引等值查询:退化临键锁为记录锁
  2. 查询结果不存在:退化临键锁为间隙锁
  3. 覆盖索引查询:退化临键锁为记录锁
-- 唯一索引等值查询(退化为记录锁)
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 意向锁的类型

类型简称含义兼容性
意向共享锁IS事务想对某些行加 S 锁与 S/IS 兼容
意向排他锁IX事务想对某些行加 X 锁只与 IS/S 兼容

5.3 意向锁的兼容性矩阵

锁类型XIXSIS
X
IX
S
IS

六、生产避坑 🟡

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)和解决方案的候选人,说明他有丰富的实战经验。


级别考察重点期望回答
P5基本分类行锁/表锁、S/X 锁
P6细分理解记录锁、间隙锁、临键锁、意向锁
P7深度实战锁退化、锁扩大、索引缺失的锁灾难、死锁排查