MySQL MVCC ⭐⭐⭐⭐⭐
面试频率:★★★★★
工作频率:★★★★☆
⚡30 秒速记
🟢 一句话
MVCC 是多版本并发控制,通过保存数据的历史版本,让不同事务根据可见性规则读取合适的数据版本,从而提高并发能力。
核心:
MVCC
│
├── 版本链
│ ↓
│ 历史版本
│
└── Read View
↓
判断哪个版本可见
重点记:
undo log
↓
历史版本
↓
版本链
↓
Read View判断可见性
↓
返回合适的数据版本
MVCC 主要用于:
快照读
例如普通:
SELECT * FROM user WHERE id = 1;
而:
SELECT * FROM user WHERE id = 1 FOR UPDATE;
属于:
当前读
需要读取符合条件的当前最新版本,并进行相应锁定。
一、什么是 MVCC?
MVCC:
Multi-Version Concurrency Control
中文:
多版本并发控制。
名字看起来复杂。
简单理解:
同一条数据可以存在多个历史版本,不同事务根据自己的可见性规则读取合适的数据版本。
例如:
数据库最新数据:
张三余额 = 500
但是某个事务:
可能仍然读取到:
张三余额 = 1000
因为:
1000
可能是这条数据的:
历史版本
二、为什么需要 MVCC? ⭐⭐⭐⭐⭐
假设:
张三余额 = 1000
事务A:
SELECT balance
FROM account
WHERE name = '张三';
第一次:
1000
这时候事务B:
UPDATE account
SET balance = 500
WHERE name = '张三';
然后:
COMMIT
现在数据库最新数据:
张三余额 = 500
但是事务A:
在 REPEATABLE READ 下进行相应的快照读时,再次查询:
SELECT balance
FROM account
WHERE name = '张三';
可能仍然看到:
1000
问题:
数据库最新值明明已经是500
↓
为什么事务A还能读取1000?
答案:
MVCC
三、MVCC 最核心解决什么问题?
如果我们希望:
事务A读取数据期间
↓
数据不要被别人修改
最简单粗暴的方式:
加锁
例如:
事务A读取
↓
锁住数据
↓
事务B想修改
↓
等待
↓
事务A结束
↓
事务B继续
问题:
并发能力下降
MVCC 的思路:
事务A
↓
想看旧版本
↓
给A看旧版本
事务B
↓
修改数据
↓
产生新版本
这样很多情况下:
读不阻塞写
写不阻塞普通快照读
所以:
MVCC 的核心价值之一就是提高数据库并发能力。
四、历史版本从哪里来? ⭐⭐⭐⭐⭐
你前面已经学过:
undo log
undo log:
不仅可以用于:
rollback
还和:
MVCC历史版本
密切相关。
例如原来:
张三余额 = 1000
事务B修改:
1000 → 500
InnoDB 会维护恢复旧版本所需要的信息。
可以粗略理解:
当前版本
张三 = 500
↓
undo
↓
历史版本
张三 = 1000
如果以前还修改过:
800 → 1000 → 500
那么可以粗略理解成:
当前版本:
500
↓
历史版本:
1000
↓
更老版本:
800
↓
...
这就形成了:
版本链。
五、什么是版本链? ⭐⭐⭐⭐⭐
同一条记录:
可能存在多个版本。
例如:
最新:
张三 = 500
↓
张三 = 1000
↓
张三 = 800
↓
更老版本...
这些版本:
按照相关信息串起来。
可以理解成:
版本链
六、版本链怎么串起来?
InnoDB 记录中有一些隐藏信息。
今天重点认识两个:
trx_id
roll_pointer
七、trx_id 是什么?
可以简单理解:
这个版本最近是被哪个事务修改的。
例如:
事务20:
把张三:
1000 → 500
那么当前版本可以粗略理解:
张三 = 500
trx_id = 20
之前版本:
张三 = 1000
trx_id = 10
所以:
trx_id
↓
记录版本对应的事务信息
八、roll_pointer 是什么?
可以简单理解:
指向 undo 中对应的历史版本信息。
例如:
当前版本
张三 = 500
trx_id = 20
↓
roll_pointer
↓
历史版本
张三 = 1000
trx_id = 10
↓
roll_pointer
↓
更老版本
张三 = 800
trx_id = 5
所以:
trx_id
+
roll_pointer
+
undo log
共同帮助构成:
版本链
九、有了版本链,怎么知道该读哪个版本? ⭐⭐⭐⭐⭐
现在数据库有:
500
↓
1000
↓
800
事务A到底应该读取:
500?
1000?
800?
不能随便选。
所以需要:
Read View
十、什么是 Read View? ⭐⭐⭐⭐⭐
简单理解:
Read View 是 MVCC 判断某个数据版本对当前事务是否可见的一套规则 / 快照信息。
例如:
事务A查询数据
↓
找到最新版本500
↓
根据Read View判断
↓
500对事务A可见吗?
↓
不符合可见性规则
↓
沿版本链继续找
↓
找到1000
↓
判断
↓
可见
↓
返回1000
所以:
版本链
↓
提供多个版本
Read View
↓
决定看哪个版本
十一、MVCC 核心流程 ⭐⭐⭐⭐⭐
例如:
数据库版本链:
500
↓
1000
↓
800
事务A查询:
SELECT
↓
快照读
↓
获取 / 使用Read View
↓
检查当前版本
↓
版本可见?
↓ ↓
是 否
↓ ↓
返回 沿版本链找
↓
再判断
↓
找到可见版本
↓
返回
所以 MVCC:
可以简单记成:
版本链
+
Read View
=
决定事务看到哪个版本
十二、为什么数据库已经是500,事务A还能看到1000? ⭐⭐⭐⭐⭐
这是理解 MVCC 最关键的问题。
假设:
事务A第一次:
张三 = 1000
事务B:
1000 → 500
↓
commit
数据库最新版本:
500
事务A再次快照读:
最新版本500
↓
根据Read View判断
↓
这个版本不符合A的可见性规则
↓
沿版本链找
↓
找到1000
↓
符合可见性规则
↓
返回1000
所以:
数据库最新值
≠
当前事务一定看到的值
这是 MVCC 非常核心的思想。
十三、MVCC 和 REPEATABLE READ ⭐⭐⭐⭐⭐
你前面学过:
REPEATABLE READ
=
可重复读
例如:
事务A:
第一次查询
1000
事务B:
修改成500
commit
事务A:
再次快照读
仍可能1000
现在可以解释原因:
REPEATABLE READ
↓
MVCC
↓
Read View
↓
版本链
↓
找到符合可见性规则的历史版本
↓
1000
所以:
REPEATABLE READ
和:
MVCC
关系非常密切。
十四、READ COMMITTED 也使用 MVCC ⭐⭐⭐⭐⭐
不要误以为:
只有REPEATABLE READ使用MVCC
不是。
MySQL InnoDB 的:
READ COMMITTED
也会使用 MVCC。
它们一个重要区别在于:
Read View 的生成 / 使用时机不同。
十五、RC 和 RR 的 Read View 区别 ⭐⭐⭐⭐⭐
这是非常高频的面试点。
READ COMMITTED
可以简单理解:
每次快照读通常会创建新的 Read View。
例如:
事务A第一次查询
↓
Read View 1
↓
1000
事务B修改
1000 → 500
commit
事务A第二次查询
↓
新的Read View 2
↓
500
所以:
第一次:1000
第二次:500
这就是:
READ COMMITTED
下可能发生:
不可重复读
十六、REPEATABLE READ
REPEATABLE READ 下:
一个事务通常会复用首次快照读建立的 Read View。
例如:
事务A第一次快照读
↓
创建Read View
↓
1000
事务B修改
1000 → 500
commit
事务A再次快照读
↓
继续使用之前的Read View
↓
根据版本链
↓
仍然读取1000
所以:
第一次:1000
第二次:1000
实现:
可重复读
十七、RC 和 RR 一张图记住 ⭐⭐⭐⭐⭐
READ COMMITTED
第一次快照读
↓
Read View 1
↓
1000
事务B修改并提交
↓
第二次快照读
↓
Read View 2
↓
500
记:
RC
=
每次快照读通常重新生成Read View
REPEATABLE READ
第一次快照读
↓
Read View 1
↓
1000
事务B修改并提交
↓
第二次快照读
↓
继续使用Read View 1
↓
1000
记:
RR
=
事务中的快照读通常复用首次快照读建立的Read View
十八、什么是快照读? ⭐⭐⭐⭐⭐
普通 SELECT:
SELECT *
FROM user
WHERE id = 1;
在没有显式锁定等特殊情况时:
通常属于:
快照读
快照读:
主要通过:
MVCC
读取符合当前 Read View 可见性规则的数据版本。
所以:
普通SELECT
↓
快照读
↓
MVCC
↓
Read View
↓
版本链
十九、什么是当前读? ⭐⭐⭐⭐⭐
例如:
SELECT *
FROM user
WHERE id = 1
FOR UPDATE;
属于:
当前读
当前读:
需要读取符合条件的:
当前最新版本。
并且:
进行相应加锁
常见当前读操作包括:
SELECT ... FOR UPDATE
UPDATE
DELETE
以及其他需要读取当前最新数据并进行锁定的操作。
二十、快照读 vs 当前读 ⭐⭐⭐⭐⭐
快照读
例如:
SELECT *
FROM user
WHERE id = 1;
核心:
MVCC
↓
Read View
↓
读取合适版本
特点:
普通读取
并发能力高
当前读
例如:
SELECT *
FROM user
WHERE id = 1
FOR UPDATE;
核心:
读取当前最新版本
+
加锁
速记:
普通SELECT
↓
快照读
↓
MVCC
SELECT ... FOR UPDATE
↓
当前读
↓
最新版本 + 锁
二十一、为什么 RR 不是永远看到旧数据?
这个非常容易理解错。
假设:
事务A:
SELECT balance
FROM account
WHERE id = 1;
快照读:
1000
事务B:
修改成500
commit
事务A:
再次普通 SELECT:
可能仍然1000
但是如果事务A执行:
SELECT balance
FROM account
WHERE id = 1
FOR UPDATE;
这是:
当前读
它需要:
读取当前最新版本
所以不能简单说:
REPEATABLE READ
=
整个事务永远只能看到第一次的数据
更准确:
MVCC 主要影响快照读;当前读需要读取当前最新版本,并结合锁机制工作。
二十二、MVCC 为什么能提高并发性能? ⭐⭐⭐⭐⭐
传统思路:
读数据
↓
加锁
↓
别人修改
↓
等待
MVCC:
事务A读取
↓
读取适合自己的版本
事务B修改
↓
生成新版本
所以很多情况下:
读和写可以并发
避免:
普通SELECT全部通过互斥锁等待
因此:
MVCC 通过多版本机制减少读写之间的冲突,提高数据库并发能力。
二十三、MVCC 和 undo log 的关系 ⭐⭐⭐⭐⭐
undo log:
事务修改数据
↓
保存旧版本相关信息
↓
形成历史版本
↓
版本链
MVCC:
快照读
↓
Read View
↓
判断当前版本是否可见
↓
不可见
↓
沿版本链寻找历史版本
所以:
undo log
↓
历史版本
↓
版本链
↓
MVCC
二十四、MVCC 和 rollback 的关系
undo log 同时服务于:
事务回滚
以及:
MVCC历史版本
可以记:
undo log
│
┌───────┴───────┐
↓ ↓
rollback MVCC
↓ ↓
恢复数据 历史版本
所以昨天学的:
undo log
和今天:
MVCC
其实是连着的。
二十五、完整知识链 ⭐⭐⭐⭐⭐
现在可以把之前的知识全部串起来:
事务隔离级别
↓
REPEATABLE READ
↓
为什么可以重复读取?
↓
MVCC
↓
┌───────────────┐
↓ ↓
版本链 Read View
↓ ↓
历史版本 可见性判断
↓ ↓
└───────┬───────┘
↓
快照读
版本链又来自:
数据修改
↓
undo log
↓
历史版本
↓
版本链
所以完整:
undo log
↓
历史版本
↓
版本链
↓
MVCC
↓
Read View
↓
快照读
↓
REPEATABLE READ
🎤 面试回答(30秒)
MVCC 是多版本并发控制,主要目的是在保证一定事务隔离性的同时提高数据库并发能力。
InnoDB 在数据修改时会通过 undo log 等机制维护历史版本,并通过版本链保存记录的不同版本。
事务进行快照读时,会根据 Read View 判断当前数据版本是否可见,如果不可见,就沿着版本链寻找符合可见性规则的历史版本。
READ COMMITTED 和 REPEATABLE READ 都会使用 MVCC,一个重要区别是 RC 的快照读通常每次会创建新的 Read View,而 RR 通常会复用事务中首次快照读建立的 Read View,因此可以实现可重复读。
🎯 面试追问
Q1:什么是 MVCC?
答:
多版本并发控制
通过:
历史版本
+
Read View
让不同事务读取合适的数据版本。
Q2:MVCC 有什么作用?
答:
减少读写冲突
↓
提高数据库并发能力
Q3:MVCC 最核心的两个东西是什么?
答:
版本链
+
Read View
Q4:历史版本从哪里来?
答:
和:
undo log
密切相关。
数据修改后:
旧版本相关信息可以通过 undo 维护。
Q5:trx_id 是什么?
答:
可以简单理解:
记录这个版本最近由哪个事务修改。
Q6:roll_pointer 是什么?
答:
可以简单理解:
指向 undo 中的历史版本信息,用于沿版本链寻找旧版本。
Q7:Read View 是什么?
答:
MVCC 用于判断某个数据版本对当前事务是否可见的一套快照信息和判断规则。
Q8:为什么数据库最新是500,事务还能读取1000?
答:
因为:
500版本
↓
Read View判断不可见
↓
沿版本链寻找
↓
找到1000
↓
1000可见
↓
返回1000
Q9:READ COMMITTED 和 REPEATABLE READ 都使用 MVCC 吗?
答:
是
一个重要区别:
RC
↓
每次快照读通常生成新的Read View
RR
↓
通常复用事务首次快照读建立的Read View
Q10:什么是快照读?
答:
普通 SELECT 通常属于快照读。
例如:
SELECT *
FROM user
WHERE id = 1;
主要通过:
MVCC
读取合适的数据版本。
Q11:什么是当前读?
答:
需要读取符合条件的:
当前最新版本
并进行相应锁定。
例如:
SELECT *
FROM user
WHERE id = 1
FOR UPDATE;
Q12:快照读和当前读有什么区别?
答:
快照读
↓
MVCC
↓
读取符合Read View的数据版本
当前读
↓
读取当前最新版本
↓
加锁
Q13:MVCC 是不是完全不用锁?
答:
不是。
MVCC:
主要解决:
快照读
的并发读取问题。
数据库中的:
UPDATE
DELETE
SELECT ... FOR UPDATE
等当前读 / 写操作:
仍然会涉及锁。
Q14:undo log 和 MVCC 有什么关系?
答:
undo log
↓
维护历史版本相关信息
↓
形成版本链
↓
MVCC可以寻找历史版本
⚠️ 工作 / 面试易错点
1. 认为 MVCC 就是不加锁
错误。
MVCC
主要用于快照读
而:
UPDATE
DELETE
SELECT ... FOR UPDATE
仍然涉及:
锁
2. 认为只有 RR 使用 MVCC
错误。
READ COMMITTED
和
REPEATABLE READ
都可以使用 MVCC。
主要区别之一:
Read View的生成 / 使用时机不同
3. 认为 RR 整个事务永远看到同一个值
不准确。
要区分:
快照读
vs
当前读
普通 SELECT:
快照读
而:
SELECT ... FOR UPDATE
属于:
当前读
4. 把 undo log 只理解成 rollback
不完整。
undo log:
rollback
+
MVCC历史版本
都非常重要。
❓ 自测
-
什么是 MVCC?
-
为什么需要 MVCC?
-
MVCC 为什么能提高数据库并发能力?
-
MVCC 最核心的两个东西是什么?
-
什么是版本链?
-
历史版本和 undo log 有什么关系?
-
trx_id 是什么?
-
roll_pointer 是什么?
-
Read View 是什么?
-
Read View 有什么作用?
-
为什么数据库最新值是500,事务还能读取1000?
-
READ COMMITTED 使用 MVCC 吗?
-
REPEATABLE READ 使用 MVCC 吗?
-
RC 和 RR 在 Read View 上有什么重要区别?
-
什么是快照读?
-
普通 SELECT 通常属于什么读?
-
什么是当前读?
-
SELECT ... FOR UPDATE 属于什么读?
-
快照读和当前读有什么区别?
-
MVCC 是不是完全不需要锁?
-
undo log 和 rollback 有什么关系?
-
undo log 和 MVCC 有什么关系?
-
REPEATABLE READ 为什么可以实现可重复读?