一、MySQL 中锁的核心概念
锁是数据库用于控制并发访问共享资源的机制,目的是保证事务的隔离性和数据一致性,防止多个事务同时修改同一数据导致脏写、脏读等问题。MySQL 的锁机制因存储引擎而异(如 MyISAM 仅支持表锁,InnoDB 支持行锁 + 表锁),其中 InnoDB 的锁体系最复杂也最核心。
二、MySQL 锁的分类(按核心维度)
1. 按锁的粒度(最核心分类)
锁粒度越小,并发度越高,但加锁开销越大、越易出现死锁。
| 锁类型 | 粒度 | 支持引擎 | 特点 | 典型场景 |
|---|---|---|---|---|
| 全局锁 | 整个数据库 | 所有引擎 | 阻塞所有写操作(读正常),加锁快、开销小;并发度极低 | 全库逻辑备份(FTWRL)、主从同步 |
| 表级锁 | 整张表 | MyISAM/InnoDB | 加锁快、无死锁;锁粒度大,并发度低 | MyISAM 写操作、InnoDB MDL 锁 |
| 行级锁 | 单行记录 | InnoDB(独有) | 锁粒度细、并发度高;加锁慢、开销大,可能死锁;基于索引加锁(无索引升级为表锁) | InnoDB 并发写操作 |
(1)全局锁
- 加锁方式:
FLUSH TABLES WITH READ LOCK (FTWRL) - 释放方式:
UNLOCK TABLES或会话断开 - 注意:与
SET GLOBAL read_only=1区别 ——read_only 对超级用户无效,FTWRL 对所有用户生效。
(2)表级锁细分
- 表锁:手动加锁(
LOCK TABLES t1 READ/WRITE),读锁共享(多个事务可加),写锁排他(仅一个事务可加)。 - 元数据锁(MDL):自动加锁(访问表时触发),保证表结构不被并发修改。读操作加 MDL 读锁(共享),ALTER TABLE 加 MDL 写锁(排他)。长事务持有 MDL 读锁会阻塞 ALTER TABLE,是生产中常见卡死场景。
(3)行级锁(InnoDB 核心)
依赖索引实现,无索引时会升级为表锁,核心变体如下:
| 行锁类型 | 作用 | 适用场景 |
|---|---|---|
| 记录锁(Record Lock) | 锁定索引上的具体行记录(如WHERE id=10,仅锁 id=10 行) | 精准匹配单行的更新 / 查询 |
| 间隙锁(Gap Lock) | 锁定索引记录之间的间隙(不包含记录本身,如 id=1、3、5,锁 (3,5) 间隙) | 防止幻读(RR 级别下生效) |
| Next-Key Lock | 记录锁 + 间隙锁(锁定索引记录 + 前面的间隙,如 id=3,锁 (1,3]) | InnoDB RR 级别默认行锁方式 |
| 插入意向锁 | 间隙锁的特例,多个事务插入同一间隙的不同记录时互不阻塞(如间隙 (3,5) 插 4 和 6) | 提升插入并发度 |
2. 按锁的读写属性(共享 / 排他)
| 锁类型 | 别名 | 兼容性(与其他锁) | 加锁方式 |
|---|---|---|---|
| 共享锁(S 锁) | 读锁 | 与 S 锁兼容,与 X 锁互斥 | 手动:SELECT ... LOCK IN SHARE MODE;自动:无(仅显式加) |
| 排他锁(X 锁) | 写锁 | 与 S/X 锁均互斥 | 自动:UPDATE/DELETE/INSERT;手动:SELECT ... FOR UPDATE |
3. 意向锁(InnoDB 表级锁,辅助行锁)
为了快速判断表中是否有行锁,减少表锁检查的开销,分为:
- 意向共享锁(IS):事务想给某行加 S 锁,先给表加 IS 锁;
- 意向排他锁(IX):事务想给某行加 X 锁,先给表加 IX 锁。
- 兼容性规则:IS 与 IS/IX 兼容,IX 与 IX 兼容;IS/IX 与表级 S 锁:IS 兼容、IX 不兼容;IS/IX 与表级 X 锁均不兼容。
4. 乐观锁 vs 悲观锁(逻辑分类)
- 悲观锁:数据库原生锁(S/X 锁),假设并发冲突必然发生,加锁阻止其他事务操作(如
SELECT ... FOR UPDATE); - 乐观锁:应用层实现(版本号 / 时间戳),假设冲突少,更新时检查版本(如
UPDATE t SET val=1 WHERE id=1 AND version=2),不通过则重试。
三、SQL 标准的四种隔离级别及 InnoDB 实现
首先明确并发访问的三大问题:
- 脏读:读取其他事务未提交的修改(数据可能回滚,无效);
- 不可重复读:同一事务多次读同一数据,其他事务修改并提交,结果不一致;
- 幻读:同一事务按条件查询,其他事务插入符合条件的新数据,查询结果多了 “幻影” 行。
InnoDB 通过MVCC(多版本并发控制)+ 锁机制实现隔离级别,核心组件:
- 版本链:每行隐藏列
DB_TRX_ID(最后修改事务 ID)、DB_ROLL_PTR(指向 undo log 的版本链); - Undo Log:保存数据历史版本,用于 MVCC 读取和事务回滚;
- Read View:事务的 “一致性视图”,定义了该事务能看到的版本范围。
1. 读未提交(Read Uncommitted, RU)
- 定义:事务可读取其他事务未提交的数据;
- 并发问题:脏读、不可重复读、幻读都存在;
- InnoDB 实现:无 MVCC 参与,读操作直接读取最新的内存 / 磁盘数据(不加任何锁),写操作加 X 锁但不阻塞读。性能最高,但一致性最差,几乎不使用。
2. 读已提交(Read Committed, RC)
- 定义:事务仅能读取其他事务已提交的数据;
- 并发问题:解决脏读,仍存在不可重复读、幻读;
- InnoDB 实现:核心是MVCC(动态 Read View) + 行级记录锁:
- Read View:每次执行 SELECT 时生成新的 Read View,确保只能看到已提交的最新版本,解决脏读;但多次读的 View 不同,导致不可重复读;
- 锁机制:禁用间隙锁(仅外键 / 唯一性检查时生效),行锁仅为记录锁(提升并发);写操作加 X 锁至事务提交,普通 SELECT 为 “快照读”(不加锁),显式读(
FOR UPDATE)加 X 锁。 - 备注:MySQL 默认隔离级别为 RR,Oracle 默认是 RC。
3. 可重复读(Repeatable Read, RR)
- 定义:同一事务内多次读取同一数据,结果始终一致;
- 并发问题:解决脏读、不可重复读;InnoDB 额外解决了幻读(SQL 标准中 RR 仍有幻读);
- InnoDB 实现:核心是MVCC(静态 Read View) + Next-Key Lock:
- Read View:事务第一次 SELECT 时生成,全程复用,确保多次读同一版本,解决不可重复读;
- Next-Key Lock:默认行锁方式(记录锁 + 间隙锁),锁定索引记录 + 间隙,阻止其他事务插入符合条件的新数据,解决幻读;
- 优化:若查询使用唯一索引(主键 / 唯一键)且匹配单行,Next-Key Lock 降级为记录锁(减少锁粒度);
- 锁机制:写操作加 X 锁至提交,普通 SELECT 为快照读,显式读加 S/X 锁。
4. 串行化(Serializable, S)
- 定义:最高隔离级别,所有事务串行执行;
- 并发问题:解决所有问题(脏读、不可重复读、幻读);
- InnoDB 实现:禁用 MVCC,所有读操作(普通 SELECT)变为 “当前读” 并隐式加 S 锁,写操作加 X 锁:
- 事务 A 的 SELECT 会锁定所有符合条件的行和间隙,事务 B 的写操作会被阻塞,直到 A 提交;
- 多个事务的读操作可并行(S 锁共享),但读与写、写与写互斥,最终事务串行执行;
- 性能最差,仅适用于数据一致性要求极高的场景(如金融核心交易)。
四、隔离级别与锁 / MVCC 对应表
| 隔离级别 | 解决的问题 | MVCC 使用 | 锁机制 |
|---|---|---|---|
| 读未提交 | 无 | 不使用 | 写加 X 锁,读不加锁 |
| 读已提交 | 脏读 | 动态 Read View(每次读生成) | 记录锁,禁用间隙锁 |
| 可重复读 | 脏读、不可重复读、幻读 | 静态 Read View(首次读生成) | Next-Key Lock(默认) |
| 串行化 | 所有问题 | 不使用 | 读加 S 锁,写加 X 锁,串行执行 |
总结
- InnoDB 的锁体系以行锁为核心,通过意向锁、Next-Key Lock 等优化锁粒度和检查效率;
- 隔离级别本质是 “一致性” 与 “并发度” 的权衡,RR 是 MySQL 默认级别,兼顾一致性和性能;
- MVCC 是 InnoDB 实现 RC/RR 的核心,通过版本链和 Read View 避免加锁读,提升并发;锁机制则解决写冲突和幻读问题。