MySQL 锁机制完全解析
深入理解 MySQL(InnoDB)中各种锁的原理、实现方式与使用场景 · 交互式可视化
🔍 锁的宏观分类
MySQL 中的"锁"可以从多个维度来分类。很多混淆来自于把概念层和实现层的锁混在一起讨论。
核心区分:概念 vs 实现
"悲观锁"和"乐观锁"是并发控制的思想/设计模式,不是 MySQL 提供的特定锁类型。而"行锁"、"表锁"、"间隙锁"等是 MySQL(主要是 InnoDB)中真实存在的锁实现。
分类维度一览
| 分类维度 | 具体类型 | 说明 |
|---|---|---|
| 按思想模式 | 悲观锁、乐观锁 | 并发控制的设计思想,不是具体锁 |
| 按锁粒度 | 表级锁、行级锁、页级锁 | 锁的作用范围大小 |
| 按兼容性 | 共享锁(S锁)、排他锁(X锁) | 锁之间是否兼容 |
| 按实现方式 | 记录锁、间隙锁、临键锁、意向锁 | InnoDB 的具体锁实现 |
| 按触发方式 | 自动加锁、显式加锁 | 语句是否自动获取锁 |
存储引擎与锁的关系
| 存储引擎 | 支持锁类型 | 说明 |
|---|---|---|
| InnoDB | 表锁 + 行锁(MVCC) | 支持事务,默认行级锁,MVCC 实现非锁定读 |
| MyISAM | 表级锁 | 不支持事务,只支持表级锁 |
| MEMORY | 表级锁 | 同 MyISAM |
| NDB | 行级锁 | 集群存储引擎 |
🤔 悲观锁 vs 乐观锁
悲观锁和乐观锁是并发控制的设计模式,在数据库层面,它们通过不同的机制来实现。
悲观锁 Pessimistic Locking
假设一定会发生并发冲突,所以在操作数据前先加锁,其他人无法同时修改。
| 实现方式(InnoDB) | SQL 示例 |
|---|---|
| SELECT ... FOR SHARE(S锁) | SELECT * FROM orders WHERE id=1 FOR SHARE; |
| SELECT ... FOR UPDATE(X锁) | SELECT * FROM orders WHERE id=1 FOR UPDATE; |
| UPDATE / DELETE(自动加X锁) | UPDATE orders SET status=1 WHERE id=1; |
注意:SELECT ... FOR UPDATE 在 InnoDB 中具体加的是"临键锁"(Next-Key Lock),而非简单的行锁。这是很多面试考点。
乐观锁 Optimistic Locking
假设大概率不会发生冲突,不加锁直接操作,提交时检查数据是否被修改过。
| 实现方式 | 说明 | 示例 |
|---|---|---|
| 版本号 Version | 每行数据带 version 字段,更新时比对 | UPDATE ... WHERE id=1 AND version=5; |
| 时间戳 Timestamp | 用更新时间判断是否有并发修改 | UPDATE ... WHERE id=1 AND updated_at='...'; |
| MVCC 快照读 | InnoDB 的普通 SELECT 就是乐观锁思想的体现 | SELECT * FROM orders WHERE id=1; |
📦 表级锁 Table-Level Lock
表级锁是 MySQL 中粒度最粗的锁,锁定整张表。MyISAM 只支持表锁,InnoDB 也支持(但一般不用)。
1. 表锁(手动)
| 锁类型 | 自身可读 | 自身可写 | 其他事务可读 | 其他事务可写 |
|---|---|---|---|---|
| 表级读锁(READ) | ✅ | ❌ | ✅ | ❌(阻塞) |
| 表级写锁(WRITE) | ✅ | ✅ | ❌(阻塞) | ❌(阻塞) |
2. 元数据锁 MDL(Metadata Lock)
MDL 是 MySQL 自动加的表级锁,用于保护表的元数据(结构)。当对表做 CRUD 时自动加 MDL 读锁;做 DDL(ALTER TABLE 等)时加 MDL 写锁。
MDL 导致的问题:一个长事务持有 MDL 读锁时,DDL 操作会被阻塞,而后续所有 CRUD 操作又会阻塞在 DDL 后面,形成"锁队列堆积"。这是线上事故的常见原因!
🔐 行级锁 Row-Level Lock
行级锁是 InnoDB 的核心特性,粒度最细,并发度最高。注意:行锁是通过锁索引记录来实现的,如果没有索引,会退化为表锁!
共享锁(S锁)与排他锁(X锁)
| 锁类型 | 含义 | 加锁方式 |
|---|---|---|
| 共享锁 S(Shared) | 读锁,其他事务可同时持有 S 锁,但不能加 X 锁 | SELECT ... FOR SHARE |
| 排他锁 X(Exclusive) | 写锁,其他事务不能加任何锁 | SELECT ... FOR UPDATE、UPDATE、DELETE |
兼容性矩阵:S 锁与 S 锁兼容,S 与 X 不兼容,X 与任何锁都不兼容。下面有交互式兼容矩阵可以体验。
🎯 记录锁 Record Lock
记录锁是最基本的行锁,锁定索引记录本身。例如 SELECT * FROM users WHERE id=5 FOR UPDATE 会对 id=5 这条索引记录加 X 型记录锁。
🕳️ 间隙锁 Gap Lock
间隙锁锁定的是索引记录之间的"间隙",不包括记录本身。用于防止其他事务往这个间隙中插入新记录,从而解决幻读问题。
重要:间隙锁只在 REPEATABLE READ 隔离级别下生效。READ COMMITTED 级别下,间隙锁会失效(除了外键和唯一性检查)。
🔗 临键锁 Next-Key Lock
临键锁是记录锁 + 间隙锁的组合,锁定的是"当前记录 + 前面的间隙"。这是 InnoDB 在 REPEATABLE READ 隔离级别下的默认行锁算法。
核心公式:Next-Key Lock = Record Lock + Gap Lock
锁定范围:(上一个记录的id, 当前记录的id](左开右闭区间)
🤝 意向锁 Intention Lock
意向锁是表级锁,由 InnoDB 自动添加,用于表明"某个事务即将或正在对表中的某些行加锁"。
没有意向锁的话,如果要给表加 X 锁,需要逐行检查是否有行锁 — 效率极低。意向锁让"InnoDB 快速判断表里是否有行锁"成为可能。
| 锁类型 | 说明 | 兼容性 |
|---|---|---|
| 意向共享锁 IS | 事务打算给某些行加 S 锁 | 与表级 S 兼容,与表级 X 不兼容 |
| 意向排他锁 IX | 事务打算给某些行加 X 锁 | 与表级 S/X 都不兼容 |
🔢 AUTO-INC 锁
AUTO-INC 锁是 InnoDB 对 AUTO_INCREMENT 列插入时使用的特殊表级锁。在插入完成后立即释放(不是事务结束才释放)。
MySQL 提供了 innodb_autoinc_lock_mode 参数来控制其行为:
| mode | 说明 |
|---|---|
| 0(traditional) | 所有 INSERT 都使用表级 AUTO-INC 锁,并发最低 |
| 1(consecutive,默认) | 简单 INSERT 使用轻量级锁,批量 INSERT 仍用表锁 |
| 2(interleaved) | 所有 INSERT 都不使用表锁,并发最高,但主从复制可能不安全 |
📄 页级锁 Page-Level Lock
页级锁锁定的是数据页(InnoDB 默认页大小 16KB)。粒度介于表锁和行锁之间。
注意:InnoDB 不支持页级锁。页级锁主要是 Berkeley DB (BDB) 存储引擎的特性。InnoDB 只有表级锁和行级锁。很多资料会混淆这一点。
💀 死锁 Deadlock
死锁是指两个或更多事务互相持有对方需要的锁,导致所有事务都无法继续执行。
🌳 锁分类树(交互式)
下面是一棵完整的交互式锁分类树,展示了 MySQL 中所有锁类型之间的层次关系。点击节点可以展开/收起。
🔗 锁兼容矩阵(交互式)
点击矩阵中的格子,查看两种锁类型之间的兼容关系说明。
📝 总结与速查
| 锁名称 | 类型 | 作用范围 | 关键特点 |
|---|---|---|---|
| 悲观锁 | 🧠 概念 | - | 先加锁再操作,FOR UPDATE 是其实现 |
| 乐观锁 | 🧠 概念 | - | 版本号/MVCC,不加锁 |
| 表级锁 | 🔧 实现 | 整张表 | MyISAM 默认,粒度粗 |
| 行级锁 | 🔧 实现 | 索引记录 | InnoDB 默认,需索引支持 |
| 记录锁 | 🔧 实现 | 单条索引记录 | 行锁的基础形式 |
| 间隙锁 | 🔧 实现 | 索引记录之间的间隙 | 防止幻读,RR 级别下有效 |
| 临键锁 | 🔧 实现 | 间隙 + 记录 | InnoDB RR 级别默认算法 |
| 意向锁 | 🔧 实现 | 表级(意向) | InnoDB 自动添加,快速判断行锁冲突 |
| MDL | 🔧 实现 | 整张表(元数据) | 保护表结构,DDL 会加写锁 |
| AUTO-INC 锁 | 🔧 实现 | 表级(自增) | 插入完成后立即释放 |
面试高频考点:
1. InnoDB 在 RR 级别下如何防止幻读?(Next-Key Lock)
2. 什么是当前读、什么是快照读?(当前读加锁,快照读走 MVCC)
3. 为什么 InnoDB 需要意向锁?(快速判断表中是否存在行锁冲突)
4. FOR UPDATE 加的是什么锁?(Next-Key Lock,不是简单的行锁)