MySQL 锁机制详解:作用、差异与生效场景
MySQL 锁机制详解:作用、差异与生效场景
本文是 MySQL 文档系列 第 2 篇。
上一篇:MySQL 5.7 到 8.0 差异详解
适用版本:InnoDB + MySQL 5.7 / 8.0 / 8.4(默认存储引擎)
为什么需要理解锁
并发事务同时读写同一份数据时,若没有协调机制,会出现 脏读、不可重复读、幻读,甚至数据损坏。MySQL(InnoDB)通过 锁 + MVCC + 隔离级别 三者配合,在一致性与性能之间做权衡。
理解各类锁的 粒度、互斥关系、持有范围,是排查 死锁、锁等待、慢 SQL、DDL 阻塞 的基础。
锁的分类总览
MySQL 的锁可以从多个维度划分,同一把锁往往同时属于多个类别:
MySQL 锁
│
┌──────────────┼──────────────┐
│ │ │
按粒度 按模式 按算法
│ │ │
全局/表/行 S / X / IS / IX Record / Gap / Next-Key
│ │
表锁/MDL 意向锁 / 自增锁
| 维度 | 常见类型 | 主要作用 |
|---|---|---|
| 粒度 | 全局锁、表锁、行锁、MDL | 控制冲突范围:越大越粗,并发越低 |
| 模式 | 共享锁 S、排他锁 X、意向锁 IS/IX | 定义读写互斥关系 |
| 算法(InnoDB 行锁) | Record、Gap、Next-Key、Insert Intention | 在索引记录上精确加锁,防幻读 |
| 逻辑 | 悲观锁、乐观锁(应用层) | 冲突时阻塞 vs 提交时 CAS 检测 |
下文按 从粗到细、从存储引擎到业务场景 的顺序展开。
1. 全局锁(Global Lock)
作用
对整个 数据库实例 加锁,使所有表处于 只读 状态(FLUSH TABLES WITH READ LOCK,FTWRL)。
典型场景
| 场景 | 说明 |
|---|---|
| 全库逻辑备份 | mysqldump --single-transaction 对 InnoDB 不需要 FTWRL;混合引擎或需要 binlog 位点时可能仍会用 |
| 主从切换前一致性读 | 旧方案中锁定全库以保证位点;现多改用 lock for backup(Percona)或 GTID |
| 禁止一切写入 | 运维级「只读模式」 |
特点与差异
- 粒度最粗:阻塞所有表的 DML、DDL(除持有锁的会话外)。
- 与表锁/行锁无关:由 Server 层管理,不经过 InnoDB 行锁机制。
- 生产慎用:高并发写入会被完全堵住;InnoDB 热备优先用 一致性快照(
single-transaction)替代。
-- 加全局读锁
FLUSH TABLES WITH READ LOCK;
-- 释放
UNLOCK TABLES;
2. 表级锁(Table Lock)
作用
锁定 整张表,同一时刻只有一个会话能对表做某些类型的写操作(取决于锁模式)。
类型
| 类型 | 触发方式 | 模式 |
|---|---|---|
| 表读锁 | LOCK TABLES t READ |
共享 |
| 表写锁 | LOCK TABLES t WRITE |
排他 |
| InnoDB 内部表锁 | 某些 DDL、无合适索引时的 DML | 自动 |
生效场景
| 场景 | 是否常用表锁 |
|---|---|
| MyISAM 引擎读写 | 是(MyISAM 只有表锁) |
| InnoDB 正常行级 DML | 否,走行锁 + 意向锁 |
LOCK TABLES 显式锁定 |
是,会话级,且会 禁用当前会话的自动提交 等行为 |
| 无索引条件的 UPDATE/DELETE | 可能 锁全表(RC 下可能锁全表行;RR 下范围更大) |
与行锁的差异
| 对比项 | 表锁 | 行锁(InnoDB) |
|---|---|---|
| 粒度 | 整表 | 索引记录 / 间隙 |
| 并发 | 低 | 高 |
| 死锁 | 较少 | 常见,需 SHOW ENGINE INNODB STATUS 分析 |
| 触发 | 显式 LOCK TABLES、MyISAM、优化器降级 |
默认 DML |
LOCK TABLES orders READ;
-- 其他会话:可读,不可写
UNLOCK TABLES;
实践建议:InnoDB 生产环境避免
LOCK TABLES;用 事务 + 行锁(SELECT ... FOR UPDATE)替代。
3. 元数据锁(MDL, Metadata Lock)
作用
保护 表结构(元数据) 的一致性,而不是表里的行数据。Server 层实现,所有引擎 都有。
模式简述
| MDL 类型 | 含义 | 与谁冲突 |
|---|---|---|
| MDL 读锁 | 访问表(SELECT/INSERT/…)时自动加 | 与 MDL 写锁冲突 |
| MDL 写锁 | ALTER TABLE、DROP TABLE 等 DDL |
与所有 MDL 读/写锁冲突 |
生效场景
| 场景 | 现象 |
|---|---|
| 长事务未提交 + 在线 DDL | DDL 在 等 MDL 写锁,后面所有查询排队 |
SELECT 慢查询挂着 |
持有 MDL 读锁,阻塞 ALTER TABLE |
gh-ost / pt-online-schema-change |
通过工具规避长时间 MDL 写锁 |
-- 会话 A:长事务
BEGIN;
SELECT * FROM users WHERE id = 1; -- 持有 users 的 MDL 读锁
-- 不提交...
-- 会话 B:
ALTER TABLE users ADD COLUMN nick VARCHAR(64); -- 等待 MDL 写锁,阻塞
与 InnoDB 行锁的差异
| 对比项 | MDL | InnoDB 行锁 |
|---|---|---|
| 保护对象 | 表定义、列、索引元信息 | 行 / 间隙数据 |
SELECT 是否加 |
是(MDL 读) | 普通 SELECT 不加行锁(靠 MVCC) |
| DDL 影响 | 直接相关 | DDL 期间还涉及 表重建、算法(INSTANT/INPLACE/COPY) |
排查
-- MySQL 8.0+
SELECT * FROM performance_schema.metadata_locks;
-- 或
SHOW PROCESSLIST; -- 看 Waiting for table metadata lock
4. 意向锁(Intention Lock)
作用
表级 的「声明锁」:事务即将在某些行上加 S 或 X 锁,用于 快速判断表级锁与行级锁是否冲突,避免逐行检查。
| 意向锁 | 含义 |
|---|---|
| IS(Intention Shared) | 事务打算给某些行加 S 锁 |
| IX(Intention Exclusive) | 事务打算给某些行加 X 锁 |
互斥关系(简表)
| IS | IX | S | X | |
|---|---|---|---|---|
| IS | ✓ | ✓ | ✓ | ✗ |
| IX | ✓ | ✓ | ✗ | ✗ |
| S | ✓ | ✗ | ✓ | ✗ |
| X | ✗ | ✗ | ✗ | ✗ |
(✓ = 兼容,✗ = 冲突)
生效场景
- 任意 InnoDB
SELECT ... LOCK IN SHARE MODE→ 先 IX/IS,再行行 S 锁。 - 任意
INSERT/UPDATE/DELETE→ 通常 IX + 行 X 锁。 - 用户不可见、自动加,理解即可,用于解释「为什么表锁与行锁能协同」。
5. InnoDB 行锁(核心)
InnoDB 行锁 加在索引上,不是加在「行数据」本身。无索引时可能 锁全表所有行 或 间隙锁全表。
5.1 共享锁 S 与排他锁 X
| 模式 | 别名 | 作用 | 典型 SQL |
|---|---|---|---|
| S | 读锁 | 可读,不可改 | SELECT ... LOCK IN SHARE MODE / FOR SHARE(8.0+) |
| X | 写锁 | 可读可写,他人不可读写 | SELECT ... FOR UPDATE;UPDATE/DELETE |
互斥:S 与 S 兼容;S 与 X、X 与 X 互斥。
生效场景
| 业务场景 | 推荐 |
|---|---|
| 读余额后不立刻改,但要防止被改 | FOR SHARE |
| 扣库存、抢单、状态机流转 | FOR UPDATE |
| 纯统计报表 | 普通 SELECT(MVCC 快照读,不加行锁) |
BEGIN;
SELECT stock FROM products WHERE id = 100 FOR UPDATE;
-- 其他事务对 id=100 的 FOR UPDATE / UPDATE 会等待
UPDATE products SET stock = stock - 1 WHERE id = 100;
COMMIT;
5.2 Record Lock(记录锁)
锁住索引上的一条确切记录。
| 项目 | 说明 |
|---|---|
| 作用 | 防止其他事务 修改或删除 该记录 |
| 隔离级别 | RC、RR 均存在 |
| 典型触发 | WHERE id = 1 等 精确匹配 且命中 唯一索引 |
索引: ... [10] [20] [30] ...
↑
Record Lock 只锁 20
5.3 Gap Lock(间隙锁)
锁住索引记录之间的间隙,不包含记录本身。
| 项目 | 说明 |
|---|---|
| 作用 | 阻止其他事务在间隙内 插入 新行,用于防幻读 |
| 隔离级别 | 主要在 RR;RC 下 默认无 Gap Lock(8.0 起可用 innodb_locking_reads 调整部分行为) |
| 典型触发 | 范围查询、非唯一索引等值查询、RR 下的 INSERT 冲突检测 |
索引: ... [10] (10,20) [20] (20,30) [30] ...
↑
Gap Lock 锁 (10,20) 这个开区间
与 Record Lock 的差异
| 对比项 | Record Lock | Gap Lock |
|---|---|---|
| 锁定对象 | 已存在的索引项 | 两个索引项之间的空隙 |
| 阻塞操作 | UPDATE/DELETE 该行 | INSERT 到该间隙 |
| RC 下 | 有 | 一般 无 |
5.4 Next-Key Lock(临键锁)
Record Lock + 该记录前的 Gap Lock,左开右闭区间 (前间隙, 当前记录]。
| 项目 | 说明 |
|---|---|
| 作用 | RR 下 默认行锁算法,防止幻读 |
| 典型触发 | WHERE id > 10 AND id < 20 扫描到的每条记录及前间隙 |
(10, 20] ← Next-Key Lock:间隙 (10,20) + 记录 20
索引: ... [10] [20] [30] ...
示例:幻读防护
-- RR,会话 A
BEGIN;
SELECT * FROM orders WHERE amount > 100 FOR UPDATE;
-- 对满足条件的行及扫描路径上的 Next-Key Lock
-- 会话 B
INSERT INTO orders (amount) VALUES (150); -- 若落在已锁间隙/记录上,则阻塞
5.5 Insert Intention Lock(插入意向锁)
一种特殊的 Gap Lock,多个事务在 同一间隙插入不同行 时 互相兼容(仍与 Gap Lock 冲突)。
| 项目 | 说明 |
|---|---|
| 作用 | 提高插入并发;插入前在间隙加意向,冲突时再等待 |
| 典型场景 | 高并发 INSERT 同一索引范围 |
5.6 行锁算法与隔离级别对照
| 隔离级别 | 快照读 | 当前读 | Gap / Next-Key | 幻读 |
|---|---|---|---|---|
| READ UNCOMMITTED | 几乎不用 | 加锁读 | 同 RC | 可能 |
| READ COMMITTED | MVCC | 加锁读,多为 Record Lock | 一般无 Gap | 可能(当前读下) |
| REPEATABLE READ(默认) | MVCC | 加锁读,Next-Key Lock | 有 | InnoDB 下当前读也防 |
| SERIALIZABLE | 普通 SELECT 也变当前读 | 全加锁 | 有 | 不会 |
RC vs RR 选型:Oracle 风格用 RC(锁少、死锁相对少);需要更强一致性且接受更大锁范围用 RR。许多互联网业务在 8.0 上使用 RC + 业务幂等 组合。
6. 自增锁(AUTO-INC Lock)
作用
保证 AUTO_INCREMENT 列 在并发 INSERT 时 ID 连续、唯一(具体连续性取决于模式)。
三种模式(innodb_autoinc_lock_mode)
| 模式 | 名称 | 行为 | 场景 |
|---|---|---|---|
| 0 | traditional | 表级 AUTO-INC 锁,直到语句/事务结束 | 兼容旧行为,并发差 |
| 1 | consecutive(默认) | 普通 INSERT 用轻量锁;INSERT ... SELECT 等批量语句仍可能表锁 |
默认平衡 |
| 2 | interleaved | 无 AUTO-INC 表锁,ID 可能 不连续 | 最高 INSERT 并发;statement 复制需注意 |
与行锁差异
- 仅保护自增计数器,不是 行级 X/S 锁。
- 高并发插入瓶颈常见于此;调 mode=2 前需确认 binlog format、主从 要求。
7. 悲观锁 vs 乐观锁(应用层)
MySQL 本身提供的是 悲观锁(冲突则等待或死锁)。乐观锁 通常由应用在 SQL 里实现:
-- 乐观锁:更新时带版本号
UPDATE account
SET balance = balance - 100, version = version + 1
WHERE id = 1 AND version = 5;
-- affected rows = 0 表示被他人抢先修改,业务重试
| 对比项 | 悲观锁(FOR UPDATE) | 乐观锁(version / CAS) |
|---|---|---|
| 冲突处理 | 阻塞等待 | 提交失败、重试 |
| 适用 | 冲突 高、必须串行 | 冲突 低、读多写少 |
| 死锁风险 | 有 | 无(无长期持锁) |
8. 其他锁与机制
8.1 谓词锁 / 空间索引锁
空间索引(R-Tree)使用 Predicate Lock,原理类似 Gap Lock,保护几何范围。
8.2 显式用户锁
SELECT GET_LOCK('my_job', 10); -- 命名锁,跨表协调
SELECT RELEASE_LOCK('my_job');
用于 跨会话、跨表 的互斥(如定时任务单实例执行),与 InnoDB 行锁无关。
8.3 死锁
InnoDB 自动检测 死锁并 回滚代价较小的事务。开发上应:
- 固定 加锁顺序(如先锁 parent 再锁 child);
- 缩短事务、避免大事务;
- 合理索引,减少锁范围。
SHOW ENGINE INNODB STATUS\G -- 查看 LATEST DETECTED DEADLOCK
-- 8.0+
SELECT * FROM performance_schema.data_locks;
SELECT * FROM performance_schema.data_lock_waits;
9. 场景速查表
| 你遇到的问题 | 可能涉及的锁 | 排查 / 缓解 |
|---|---|---|
ALTER TABLE 一直 Waiting |
MDL 写锁被长查询占着 | 杀长查询;用 online DDL 或 gh-ost |
| 两个 UPDATE 互相等 | 行 X 锁、Next-Key Lock | 统一访问顺序;拆小事务 |
| 范围 SELECT 后 INSERT blocked | Gap / Next-Key(RR) | 改 RC;或缩小 FOR UPDATE 范围 |
| 无索引 UPDATE 锁很多行 | 锁全表扫描到的行 | 加合适索引 |
| 备份要停写 | 全局锁 / FTWRL | --single-transaction(InnoDB) |
| 扣库存超卖 | 未用 FOR UPDATE 或隔离级别不当 | 悲观锁 + 唯一约束;或乐观锁 |
| INSERT 并发低 | AUTO-INC 锁 mode=1 | 评估 mode=2 |
| 定时任务多实例重复跑 | — | GET_LOCK |
10. 加锁读 vs 快照读(复习)
| 读类型 | SQL 示例 | 是否加 InnoDB 行锁 | 读到的数据 |
|---|---|---|---|
| 快照读 | 普通 SELECT |
否(MVCC) | 一致性视图(版本链) |
| 当前读 | FOR UPDATE / FOR SHARE / DML |
是 | 最新已提交 + 锁 |
锁解决的是写冲突与幻读;MVCC 解决的是读不阻塞写。 二者在 RR 下配合,才构成 InnoDB 默认并发模型。
11. 配置与版本差异提示
| 项 | 5.7 / 8.0 注意点 |
|---|---|
| 默认隔离级别 | InnoDB 默认 RR |
FOR SHARE |
8.0 替代 LOCK IN SHARE MODE 语法 |
performance_schema.data_locks |
8.0 替代 information_schema.innodb_locks |
| RC + 半一致读 | 减少 UPDATE 等锁等待,但无 Gap Lock |
| Instant DDL | 8.0 部分 ADD COLUMN 减少 MDL 持有时间(仍要 MDL) |
官方参考
小结
| 锁 | 粒度 | 核心作用 | 牢记场景 |
|---|---|---|---|
| 全局锁 | 实例 | 全库只读 | 逻辑备份、运维 |
| 表锁 | 表 | 整表互斥 | MyISAM、LOCK TABLES |
| MDL | 表元数据 | 保护表结构 | DDL vs 长查询 |
| 意向锁 IS/IX | 表 | 声明行锁意图 | 自动,理解即可 |
| 行 S/X | 索引记录 | 读写互斥 | 扣库存、状态流转 |
| Record / Gap / Next-Key | 索引记录与间隙 | 防改、防插、防幻读 | RR 范围查询 |
| 自增锁 | 计数器 | ID 唯一 | 高并发 INSERT |
| GET_LOCK | 逻辑名 | 应用协调 | 分布式单跑 |
实践原则:默认 InnoDB + 合适索引 + 短事务;需要强一致写路径用 当前读;DDL 避开 MDL;排查锁问题优先 8.0 的 data_locks / data_lock_waits。
浙公网安备 33010602011771号