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 TABLEDROP 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 UPDATEUPDATE/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

posted @ 2026-06-26 15:44  一个老码农  阅读(25)  评论(0)    收藏  举报