MySQL 系统学习 第四阶段:MySQL 高级 第六节:MVCC(Multi-Version Concurrency Control,多版本并发控制)
前面我们学习:
- 事务:保证一组操作的一致性
- 锁:解决多个事务同时修改数据的问题
但是有一个问题:
如果所有查询都加锁,数据库还能高并发吗?
答案:
不能。
所以 InnoDB 引入了:
MVCC
让读操作不需要加锁,也能读取一致的数据版本。
一、为什么需要 MVCC?
假设:
用户表:
id | name | age 1 | Tom | 20现在两个事务:
事务A:
查询:
SELECT * FROM user WHERE id=1;事务B:
修改:
UPDATE user SET age=21 WHERE id=1;如果没有 MVCC:
事务A读取时:
必须等待事务B完成。
结果:
并发能力下降。
有 MVCC:
事务A:
读取旧版本:
age=20事务B:
修改新版本:
age=21两个事务互不影响。
二、MVCC 的核心思想
一句话:
数据库保存数据的多个版本,读取时根据事务规则选择合适版本。
例如:
数据:
版本1: age=20 版本2: age=21 版本3: age=22不同事务:
看到不同版本。
类似 Git:
commit1 | commit2 | commit3可以查看不同历史版本。
三、InnoDB 实现 MVCC 依赖什么?
主要依靠三个东西:
1. 隐藏字段
2. Undo Log(回滚日志)
3. Read View(读视图)
⭐⭐⭐⭐⭐
四、隐藏字段
InnoDB 每一行数据实际上有隐藏字段。
例如:
用户表:
CREATE TABLE user( id INT, name VARCHAR(20) );实际上:
InnoDB 保存类似:
id name 隐藏字段: ---------------- DB_TRX_ID DB_ROLL_PTR① DB_TRX_ID
事务ID。
表示:
哪个事务修改了这条数据。
例如:
事务100修改 DB_TRX_ID=100② DB_ROLL_PTR
回滚指针。
指向:
上一个版本的数据。
形成版本链。
五、Undo Log(回滚日志)
Undo Log:
作用:
保存数据修改之前的版本。
例如:
原数据:
age=20事务A修改:
UPDATE user SET age=21;数据库保存:
Undo Log:
age=20现在:
当前数据:
age=21历史数据:
age=20形成:
当前版本 | | Undo Log | 旧版本六、版本链是什么?
例如:
数据不断修改:
第一次:
age=20事务101:
age=21事务102:
age=22最终:
age=22 ↓ age=21 ↓ age=20这就是:
版本链。
七、Read View(读视图)⭐⭐⭐⭐⭐
这是 MVCC 最核心概念。
Read View:
事务进行普通查询时创建的一份可见性规则。
简单理解:
它决定:
我应该看到哪个版本的数据。
例如:
当前数据:
版本3 | 版本2 | 版本1事务A:
创建 Read View。
规则:
可能看到:
版本2而不是最新:
版本3八、MVCC 如何判断数据是否可见?
InnoDB 会比较:
数据版本中的:
DB_TRX_ID和:
当前事务的 Read View。
简单理解:
如果:
修改数据的事务已经提交:
可以看到。
如果:
修改数据的事务还没有提交:
不能看到。
九、MVCC 解决什么问题?
主要解决:
1. 读写冲突
例如:
事务A:
读取。
事务B:
修改。
不用互相等待。
2. 提高并发
传统方式:
读 | 加锁 | 等待写MVCC:
读 | 读取历史版本十、MVCC 与隔离级别关系
MySQL InnoDB 默认:
REPEATABLE READ可重复读。
为什么可以做到?
因为:
MVCC。
例如:
事务A:
第一次查询:
余额=1000事务B:
修改:
余额=500提交。
事务A:
再次查询:
仍然:
余额=1000因为:
使用同一个 Read View。
十一、MVCC 如何解决不可重复读?
不可重复读:
原因:
第二次读取看到最新数据。
MVCC:
第一次查询:
创建 Read View:
看到版本1第二次查询:
继续使用:
版本1所以:
结果一致。
十二、MVCC 和锁的区别
很多人容易混淆。
| MVCC | 锁 | |
|---|---|---|
| 解决问题 | 读一致性 | 并发修改冲突 |
| 方式 | 版本控制 | 阻塞等待 |
| 是否阻塞 | 通常不阻塞 | 可能阻塞 |
| 主要对象 | 读操作 | 写操作 |
例如:
普通查询:
SELECT * FROM user;使用:
MVCC。
修改:
UPDATE user SET age=20;使用:
锁。
十三、普通 SELECT 和 FOR UPDATE 区别
普通查询:
SELECT * FROM user WHERE id=1;特点:
- 不加锁
- 使用 MVCC
- 读取快照数据
当前读:
SELECT * FROM user WHERE id=1 FOR UPDATE;特点:
- 加锁
- 读取最新数据
- 用于修改前查询
例如:
库存扣减:
必须:
SELECT stock FROM product WHERE id=1 FOR UPDATE;不能使用普通 SELECT。
十四、企业开发中的应用
场景1:订单查询
普通:
SELECT * FROM orders WHERE user_id=100;使用:
MVCC。
高并发:
没问题。
场景2:库存修改
需要:
SELECT stock FROM product WHERE id=1 FOR UPDATE;使用:
锁。
保证不会超卖。
十五、本节总结
| 概念 | 作用 |
|---|---|
| MVCC | 多版本并发控制 |
| 目的 | 提高数据库并发能力 |
| Undo Log | 保存历史版本 |
| 版本链 | 保存数据变化记录 |
| Read View | 判断版本是否可见 |
| 普通SELECT | 使用MVCC |
| FOR UPDATE | 使用锁 |
| RR隔离级别 | 依赖MVCC实现可重复读 |
🎯 本节核心一句话:
MVCC 通过保存数据历史版本,让读操作不需要加锁也能看到一致的数据,是 InnoDB 高并发能力的核心机制。
