2026年7月11日 · 5 分钟阅读
MySQL 锁机制:全局锁、表锁、行锁、间隙锁、临键锁与死锁分析
梳理 MySQL 锁体系,包括读写锁、全局锁、表级锁、MDL、自增锁、InnoDB 行锁、记录锁、间隙锁、Next-Key Lock、插入意向锁、意向锁和死锁规避。
MySQL 的锁机制,是事务隔离和并发控制的基础。很多线上问题,比如更新阻塞、DDL 卡住、死锁、接口超时,背后都和锁有关。
理解 MySQL 锁,要先区分两个维度:锁的模式和锁的粒度。
读锁和写锁
最基础的锁模式是共享锁和排他锁。
| 锁 | 也叫 | 特点 |
|---|---|---|
| 共享锁 | 读锁 S Lock | 多个事务可以同时读 |
| 排他锁 | 写锁 X Lock | 会阻塞其他读锁和写锁 |
读锁之间不互斥,写锁和读锁、写锁和写锁之间互斥。
按粒度分类
MySQL 锁按粒度可以分为:
| 锁粒度 | 说明 | 实现层 |
|---|---|---|
| 全局锁 | 锁整个数据库实例 | Server 层 |
| 表级锁 | 锁一张表 | Server 层 |
| 行级锁 | 锁行记录或索引范围 | 存储引擎层,主要是 InnoDB |
锁粒度越大,开销越小,但并发能力越差;锁粒度越小,并发能力越好,但实现更复杂。
全局锁
全局锁会让整个数据库实例进入只读状态。常见命令:
flush tables with read lock;
加全局锁后,DML、DDL、事务提交等写操作都会被阻塞。
全局锁典型用途是全库逻辑备份。但现代 MySQL 更常用支持一致性视图的备份方式,尽量避免长时间全局锁影响业务。
表级锁
表级锁锁住整张表。
MySQL 表级锁包括:
- 表读锁。
- 表写锁。
- 元数据锁 MDL。
- 自增锁 AUTO-INC Lock。
表读锁会阻塞写,表写锁会阻塞读和写。
MDL 元数据锁
MDL 是 Metadata Lock,用于保护表结构元数据。
当执行查询、更新时,会持有 MDL 读锁;执行 DDL 时,需要 MDL 写锁。
典型问题是:一个长事务查询了某张表但一直不提交,后续 DDL 等待 MDL 写锁,再后续普通查询也可能排队等待,导致业务大量阻塞。
所以线上执行 DDL 前,一定要关注长事务。
自增锁
自增锁用于保证自增列分配的正确性。
插入数据时,InnoDB 需要为自增字段分配递增值。不同插入模式下,自增锁的持有时间和并发能力会不同。
它通常不是业务开发最常直接感知的锁,但在高并发批量插入场景中会影响吞吐。
InnoDB 行锁
InnoDB 行锁是通过索引实现的。准确说,它锁的是索引记录,而不是直接锁物理行。
这点非常关键:如果更新条件没有命中索引,可能扫描大量记录并加锁,甚至造成看起来像锁表的效果。
常见行锁包括:
- 记录锁。
- 间隙锁。
- 临键锁。
- 插入意向锁。
记录锁
记录锁锁住某条索引记录。
例如:
update user set name = '曹操' where id = 1;
如果 id 是主键,InnoDB 会锁住主键索引上 id = 1 这条记录。
间隙锁
间隙锁锁的是索引记录之间的间隙。
例如索引中存在 10 和 20,那么 (10, 20) 这个范围就是间隙。间隙锁的目的是阻止其他事务在这个范围插入新记录。
间隙锁主要用于解决幻读问题。
临键锁 Next-Key Lock
临键锁是记录锁和间隙锁的组合。
Next-Key Lock = Record Lock + Gap Lock
它既锁住索引记录,也锁住记录前面的间隙。
InnoDB 在 RR 隔离级别下,很多范围查询加锁会使用 Next-Key Lock,防止其他事务插入满足条件的新记录。
插入意向锁
插入意向锁是一种特殊的间隙锁,用于表示事务准备在某个间隙插入记录。
多个事务如果插入同一间隙内不同位置,通常可以并发;如果和已有间隙锁冲突,就需要等待。
插入意向锁不是“意向锁”的同义词,它是行级锁体系里的概念。
意向锁
意向锁是表级锁,用于表示事务接下来要在表中某些行上加锁。
常见有:
- 意向共享锁 IS。
- 意向排他锁 IX。
它的作用是协调表锁和行锁。比如一个事务已经对表中某些行加了排他锁,另一个事务想对整张表加写锁时,可以通过意向锁快速判断是否冲突,而不用逐行检查。
RC 和 RR 下加锁差异
Read Committed 和 Repeatable Read 下,InnoDB 加锁行为不同。
一般来说:
- RC 更少使用间隙锁,并发性更好。
- RR 为了解决幻读,范围加锁时更容易使用间隙锁或 Next-Key Lock。
这也是为什么同一条 SQL,在不同隔离级别下可能锁住不同范围。
为什么索引会影响锁范围
InnoDB 行锁基于索引。如果查询条件能精确命中唯一索引,锁范围通常较小;如果是范围查询,锁范围会扩大;如果没有合适索引,可能扫描并锁住更多记录。
所以优化锁冲突,很多时候不是先改事务隔离级别,而是先检查 SQL 是否走了正确索引。
死锁是什么
死锁是两个或多个事务互相等待对方持有的锁,导致谁都无法继续。
典型场景:
事务 A 锁住 id=1,等待 id=2
事务 B 锁住 id=2,等待 id=1
MySQL 默认会主动检测死锁,并回滚其中一个影响较小的事务,让另一个事务继续执行。
如何减少死锁
减少死锁的常见方法:
- 保持事务短小,尽快提交。
- 多个事务按相同顺序访问资源。
- 尽量使用主键或唯一索引更新。
- 避免大范围扫描加锁。
- 避免在事务中执行耗时业务逻辑。
- 合理拆分批量更新。
- 对死锁异常做好重试。
死锁不一定能完全避免,关键是降低概率并让业务具备重试能力。
小结
MySQL 锁机制可以这样理解:
- 读锁共享,写锁排他。
- 全局锁影响整个实例,表锁影响整张表,行锁影响索引记录和范围。
- MDL 常导致 DDL 被长事务阻塞。
- InnoDB 行锁基于索引实现。
- 间隙锁和 Next-Key Lock 用于解决幻读。
- 死锁的关键是事务访问资源顺序交叉。
掌握锁机制后,排查“为什么 SQL 卡住”就有了方向。