MySQL 锁机制是 InnoDB 引擎实现高并发、事务安全的核心,主要通过全局锁、表级锁、行级锁三级粒度,配合共享/排他锁、意向锁、间隙锁、临键锁等类型,解决并发事务下的脏读、不可重复读、幻读问题。下面从分类、原理、算法、实战、死锁五个维度全面详解。
一、锁的核心分类(按粒度)
1. 全局锁(Global Lock)
- 作用:锁定整个 MySQL 实例,全库只读。
- 命令:
FLUSH TABLES WITH READ LOCK (FTWRL)。 - 场景:全库逻辑备份(mysqldump)。
- 特点:极重,阻塞所有 DML/DDL,谨慎使用。
2. 表级锁(Table-Level Lock)
锁定整张表,开销小、加锁快、并发低。
(1) 表读锁/表写锁
- 命令:
LOCK TABLES t READ; -- 读锁:其他事务可读,不可写 LOCK TABLES t WRITE; -- 写锁:独占,阻塞所有读写 - 引擎:MyISAM 默认;InnoDB 支持但少用。
(2) 元数据锁 MDL(Metadata Lock)
- 隐式自动加锁,无需手动操作。
- 读锁:查询(SELECT)时加,兼容其他读锁。
- 写锁:DDL(ALTER、DROP)时加,互斥所有锁。
- 高危场景:长查询持有 MDL 读锁 → DDL 被阻塞 → 后续所有查询被堵 → 连接池打满、业务雪崩。
- 规避:DDL 必须避峰;使用 gh-ost/pt-online-schema-change 在线改表。
(3) 意向锁(Intention Lock)
- InnoDB 为行锁+表锁共存设计的表级辅助锁。
- 意向共享锁 IS:事务准备对某些行加 S 锁。
- 意向排他锁 IX:事务准备对某些行加 X 锁。
- 作用:快速判断表上是否有行锁冲突,避免全表扫描检查。
- 兼容性:
- IS/IX 之间互相兼容。
- IS 与表 X 锁互斥;IX 与表 X 锁互斥。
3. 行级锁(Row-Level Lock)
InnoDB 核心,锁定单行/索引范围,粒度最细、并发最高。
- 依赖索引:无索引 → 全表锁(行锁退表锁)。
- 基础类型:
- 共享锁 S(Shared Lock)
- 允许多事务同时读,阻塞 X 锁。
- 语法:
SELECT ... LOCK IN SHARE MODE;。
- 排他锁 X(Exclusive Lock)
- 独占读写,阻塞 S 和 X。
- 自动加锁:UPDATE/DELETE/INSERT。
- 手动:
SELECT ... FOR UPDATE;。
- 共享锁 S(Shared Lock)
二、InnoDB 行锁三大算法(RR 隔离级别默认)
1. 记录锁 Record Lock
- 锁定单个索引记录。
- 触发:唯一索引 + 精准等值查询(命中)。
SELECT * FROM user WHERE id=10 FOR UPDATE; -- 仅锁 id=10
2. 间隙锁 Gap Lock
- 锁定索引之间的“空隙”(不包含记录)。
- 作用:防止幻读(禁止区间插入)。
- 示例(id=1,3,5):
SELECT * FROM user WHERE id BETWEEN 1 AND 5 FOR UPDATE; -- 锁间隙:(1,3)、(3,5) - 特性:间隙锁之间兼容,仅与插入意向锁互斥。
3. 临键锁 Next-Key Lock
- 记录锁 + 间隙锁(左开右闭区间)。
- InnoDB 默认行锁算法。
- 示例(id=1,3,5):
SELECT * FROM user WHERE id>3 FOR UPDATE; -- 锁:(3,5]、(5,+∞) - 作用:彻底解决 RR 级别幻读。
4. 插入意向锁 Insert Intention Lock
- 插入前加的间隙锁,表示“打算在某间隙插入”。
- 互斥:与同间隙的间隙锁/临键锁冲突。
三、锁与事务隔离级别
| 隔离级别 | 锁行为 | 解决问题 | 存在问题 |
|---|---|---|---|
| 读未提交 RU | 基本不加锁 | 无 | 脏读、不可重复读、幻读 |
| 读已提交 RC | 仅记录锁,无间隙锁 | 脏读 | 不可重复读、幻读 |
| 可重复读 RR(默认) | 记录+间隙+临键锁全生效 | 脏读、不可重复读、幻读 | — |
| 串行化 Serializable | 全表加锁 | 所有问题 | 并发极低 |
四、加锁规则(高频考点)
- 最小原则:唯一索引等值命中 → 降级为记录锁(关闭间隙)。
- 范围原则:范围查询(>、<、BETWEEN)→ 临键锁全区间。
- 无索引原则:WHERE 未用索引 → 全表每一行加 X 锁(表锁效果)。
- 扫描原则:锁加在扫描过的索引上,不只是匹配行。
- 死锁原则:行锁会产生死锁;表锁不会。
五、死锁与排查
1. 死锁成因
- 互斥、请求与保持、不可剥夺、循环等待四大条件同时满足。
- 典型场景:
- T1:锁 A → 等待 B
- T2:锁 B → 等待 A → 循环死锁。
2. 死锁处理
- 超时机制:
innodb_lock_wait_timeout(默认 50s)。 - 死锁检测:InnoDB 主动检测,回滚代价最小事务。
3. 排查命令
-- 查看锁等待
SHOW ENGINE INNODB STATUS;
-- 查看当前锁
SELECT * FROM performance_schema.data_locks;
-- 查看锁等待
SELECT * FROM performance_schema.data_lock_waits;
4. 死锁预防
- 顺序访问:所有事务按相同顺序更新数据。
- 快进快出:事务越小越好,及时 COMMIT/ROLLBACK。
- 避免长查询:减少 MDL 与行锁持有时间。
- 合理索引:防止行锁变表锁。
六、实战要点(避坑)
- FOR UPDATE 必须走索引,否则全表锁。
- RR 级别默认临键锁,范围查询易锁大量间隙。
- MDL 写锁是 DDL 最大风险,务必避峰。
- UPDATE/DELETE 无索引 = 全表锁。
- 高并发写:拆小事务、用唯一索引、减少锁范围。
七、总结
- 全局锁:备份用,极重。
- 表锁/MDL:并发低,DDL 高危。
- InnoDB 行锁:
- S/X 基础 + IS/IX 表级协调。
- 记录/间隙/临键三算法(RR 默认临键锁)。
- 索引是行锁灵魂。
- 核心目标:并发与一致性平衡,解决脏读/不可重复读/幻读。