这是《MySQL 8.0 源码与内核实战》的独立章节版。本章从概念、实操和生产排查三个视角展开,代码块保留了原书可直接运行的版本。 InnoDB 锁系统管理表锁、索引记录锁、间隙锁、next-key 锁和插入意向锁,与隔离级别、索引结构和事务顺序密切相关。
20.1 锁层次
Server MDL
-> InnoDB table lock/intention lock
-> index record lock
|-- gap lock
|-- next-key lock
+-- insert intention lock
行锁加在索引记录上。没有可用索引时,扫描范围可能扩大,导致锁住大量记录或间隙。
20.2 锁模式
| 模式 | 说明 |
|---|---|
| S | 共享 |
| X | 排他 |
| IS | 意向共享 |
| IX | 意向排他 |
| AI | 自增锁 |
兼容性遵循 S/S 兼容,X 与其他基本冲突,意向锁用于表级快速判断。
20.3 锁类型
| 类型 | 说明 |
|---|---|
| record lock | 锁索引记录 |
| gap lock | 锁记录前间隙 |
| next-key lock | record + gap |
| insert intention | 插入前等待间隙冲突 |
| auto-inc lock | 自增分配 |
RR 下范围扫描可能使用 gap/next-key 防止幻读;RC 下间隙锁使用范围明显缩小。
20.4 加锁示例
CREATE TABLE t(
id BIGINT PRIMARY KEY,
a INT,
KEY idx_a(a)
) ENGINE=InnoDB;
BEGIN;
SELECT * FROM t WHERE a BETWEEN 10 AND 20 FOR UPDATE;
在 RR 下可能锁定:
idx_a 中满足条件的记录及间隙
其他事务在对应间隙插入会被阻塞。
20.5 锁等待
SELECT * FROM information_schema.innodb_trx;
SELECT * FROM performance_schema.data_locks;
SELECT * FROM performance_schema.data_lock_waits;
SELECT * FROM sys.innodb_lock_waits;
排查顺序:
- 找 waiting thread;
- 找 blocking thread;
- 查看锁对象和模式;
- 查看 SQL 和事务开始时间;
- 判断是否长事务;
- 决定等待、回滚或 kill;
- 优化索引和访问模式。
20.6 死锁
trx A holds row1 waits row2
trx B holds row2 waits row1
处理:
- 检测等待环;
- 选择回滚代价较小事务;
- 写入死锁日志;
- 另一事务继续。
SHOW VARIABLES LIKE 'innodb_print_all_deadlocks';
减少死锁:
- 表和行按相同顺序访问;
- 缩短事务;
- 索引精确;
- 避免锁后再等待外部资源;
- 重试幂等操作;
- 控制并发批处理。
20.7 自增锁
SHOW VARIABLES LIKE 'innodb_autoinc_lock_mode';
| 模式 | 特点 |
|---|---|
| 0 | 传统模式 |
| 1 | 连续,简单插入更轻量 |
| 2 | 交错,性能高,语句序列可能不连续 |
主从复制和 binlog 格式会影响可接受模式。
20.8 源码入口
| 文件 | 职责 |
|---|---|
lock0lock.cc |
锁系统 |
lock0priv.h |
内部结构 |
row0sel.cc |
读取加锁路径 |
row0ins.cc |
插入冲突 |
断点:
b lock_rec_lock
b lock_deadlock_occurs
本章小结
InnoDB 行锁绑定索引记录,隔离级别和访问路径共同决定锁范围。排查锁要看等待关系、事务、索引和 SQL;优化锁要缩短事务、精确索引并统一访问顺序。
思考题
- 为什么没有索引会扩大锁范围?
- gap lock 和 next-key lock 的区别是什么?
- RR 和 RC 下间隙锁行为有什么差异?
- 如何定位 blocking thread?
- 死锁的常见预防方法有哪些?