谈谈 InnoDB 中的表级锁、页级锁、行级锁?
结论:锁粒度是「并发度与开销」的权衡。表级锁开销小、冲突高;行级锁开销大、并发高;页级锁介于两者之间,但 InnoDB 根本没有页锁——它是 BDB 引擎的历史产物。InnoDB 的完整答案是:行级锁为主,配合意向锁实现表级与行级的共存,另有 MDL 与 AUTO-INC 锁处理元数据与自增。
一、三种粒度
| 粒度 | 代表引擎 | 加锁开销 | 冲突概率 | 并发度 | 典型场景 |
|---|---|---|---|---|---|
| 表级锁 | MyISAM、MEMORY | 小 | 高 | 低 | 全表统计、DDL |
| 页级锁 | BDB(已废弃) | 中 | 中 | 中 | 历史引擎,InnoDB 不使用 |
| 行级锁 | InnoDB | 大 | 低 | 高 | OLTP 点更新 |
InnoDB 里「表级锁」依然存在,但它主要服务于「让表锁与行锁能共存」——这就是意向锁。
二、InnoDB 锁的全景
InnoDB 锁
├── 行级锁(锁索引记录,不是锁行本身)
│ ├── 记录锁 Record Lock :锁住某条索引记录
│ ├── 间隙锁 Gap Lock :锁住两条记录之间的开区间
│ ├── 临键锁 Next-Key Lock :记录锁 + 前面的间隙(左开右闭)
│ └── 插入意向锁 Insert Intention:插入前的间隙锁,等待时不阻塞其他插入意向
├── 表级锁
│ ├── 意向共享锁 IS / 意向排他锁 IX :声明「我要在某行上加锁」
│ ├── AUTO-INC 锁 :自增值分配
│ └── MDL 元数据锁 :保护表结构,DML 取共享、DDL 取排他
└── 其他
├── 谓词锁(空间索引)
└── 页级锁:InnoDB 无
三、next-key lock 的加锁规则
在可重复读下,UPDATE/DELETE/SELECT ... FOR UPDATE 走非唯一索引或范围条件时,加的是临键锁,用来阻止区间内的插入(防幻读)。
-- 假设 id 有 1、5、10 三条记录
BEGIN;
SELECT * FROM t WHERE id BETWEEN 3 AND 8 FOR UPDATE;
-- 加锁范围(左开右闭):
-- (-∞, 1] 之外的 (1, 5] 与 (5, 10] 被锁住
-- 插入 id=4 / id=6 都会被阻塞
查看实际加锁情况:
SELECT engine_transaction_id, object_name, index_name,
lock_type, lock_mode, lock_data
FROM performance_schema.data_locks
ORDER BY engine_transaction_id;
lock_mode 里出现 X,GAP 是间隙锁,X,REC_NOT_GAP 是纯记录锁,X 通常就是临键锁。
四、不同隔离级别下的差异
| 隔离级别 | 间隙锁 | 行锁范围 | 说明 |
|---|---|---|---|
| 读未提交 | 无 | 最小 | 几乎不加读锁 |
| 读已提交 | 无 | 仅命中行 | 锁范围小、死锁少 |
| 可重复读 | 有 | 命中行 + 间隙 | 默认级别,防幻读 |
| 串行化 | 有 | 读也加共享锁 | 并发最低 |
-- 切到 RC 观察:同样的范围更新,间隙锁消失
SET SESSION TRANSACTION ISOLATION LEVEL READ-COMMITTED;
五、几类容易混淆的「表锁」
- 意向锁:由 InnoDB 自动加,
SELECT ... FOR UPDATE前先给表加 IX。它的作用是让「表级请求」不必逐行检查是否有人持行锁,直接看意向锁即可。 - AUTO-INC 锁:
innodb_autoinc_lock_mode控制,0 传统表锁、1 连续、2 交错(8.0 默认)。批量插入时模式 0/1 会阻塞并发插入。 - MDL:DDL 与 DML 之间的协调者。长事务持有 MDL 共享锁时,
ALTER TABLE会等待,并把它后面的所有请求一起堵住——这也是「加索引锁表」的真实原因。
常见追问
- InnoDB 有页级锁吗? 没有。页级锁是 BDB 引擎的概念,InnoDB 只有行级与表级(含意向锁、MDL、AUTO-INC)。
- 行锁锁的是什么? 锁的是索引记录。没有索引时,扫描到的每一行都会被锁,表现为「锁表」。
- 意向锁会阻塞谁? 只与表级锁互斥:
LOCK TABLES ... WRITE需要等待所有意向锁释放。 - 间隙锁一定降低并发吗? 会,插入被阻塞、死锁概率上升;所以高并发写入常把隔离级别降为 RC。
SELECT会加锁吗? 普通快照读不加锁;FOR UPDATE/FOR SHARE与串行化下的读会加锁。- 如何减少锁冲突? 走索引缩小范围、缩短事务、固定加锁顺序、必要时降级到 RC。
小结
回答这题的关键是「纠偏 + 补全」:先指出 InnoDB 没有页锁,再说明它用行级锁保证并发、用意向锁让表级与行级共存、用 MDL 保护结构、用 AUTO-INC 锁管理自增。最后补一句「行锁锁的是索引记录,没索引就退化成锁表」,基本就到位了。