MVCC

MySQL InnoDB 的事务隔离是通过「锁 + MVCC(多版本并发控制)」共同实现的。

  • 写操作:主要靠

  • 读操作:主要靠 MVCC

MVCC 的核心思想:

通过维护数据的多个版本,使读写不冲突,从而提高并发性能。


一、MVCC 的三个核心组件

这里是 100%面试必背结构:

MVCC 依赖三个东西:

├─ 隐藏字段
├─ undo log
└─ ReadView

我们一个一个解释。


二、隐藏字段(Hidden Columns)

InnoDB 每一行数据 都会自动加两个隐藏字段

row
├─ 数据列
├─ trx_id
└─ roll_pointer

1 trx_id(事务ID)

trx_id

含义:

最后一次修改这行数据的事务ID

举例:

事务1 更新了 rowA
trx_id = 1

事务2 又更新 rowA
trx_id = 2

说明:

当前版本是事务2产生的。


2 roll_pointer(回滚指针)

roll_pointer

作用:

指向 undo log 里的旧版本

比如:

row 最新版本

undo log version1

undo log version0

于是形成一个:

版本链
version3 -> version2 -> version1 -> version0

这个链叫:

Version Chain(版本链)


三、undo log(回滚日志)

这个东西非常关键。

undo log 主要有两个作用:

1 支持事务回滚

如果事务失败:

rollback

就通过 undo log 恢复数据。


2 存储历史版本(MVCC核心)

每次 update:

old value -> undo log

所以数据形成:

最新数据

旧版本

更旧版本

例如:

余额 = 100
事务A -> 改成 120
事务B -> 改成 150

版本链:

150 (trx_id=3)

120 (trx_id=2)

100 (trx_id=1)


四、ReadView(读视图)

这个才是 MVCC最核心最难的地方

ReadView 的作用是:

决定当前事务能看到哪个版本。

换句话说:

在版本链里选一个版本给你读。


ReadView里最重要的信息

ReadView里面记录:

m_ids 当前活跃事务id列表
min_trx_id 最小活跃事务
max_trx_id 下一个事务id
creator_trx_id 创建ReadView的事务

但是你面试只要记住 三个规则
Pasted image 20260313144957


五、版本可见性判断(面试重点)

当你读一行数据时:

MySQL 会:

沿着版本链往下找

直到找到 对当前事务可见的版本

判断规则:

情况1

trx_id < min_trx_id

说明:

这个事务 早就提交了

可以读。


情况2

trx_id > max_trx_id

说明:

这个版本是 未来事务产生的

不能读。


情况3

min_trx_id <= trx_id <= max_trx_id

如果:

trx_id ∈ m_ids

说明:

事务 还没提交

不能读。

否则:

可以读。


六、完整流程(真正理解MVCC)

我们用一个真实例子走一遍。

假设:

事务A 开始
事务B 修改数据
事务C 查询数据

原始数据:

balance = 100
trx_id = 1


Step1

事务B:

update balance = 200
trx_id = 2

版本链:

200 (trx_id=2)

100 (trx_id=1)


Step2

事务C查询:

此时生成:

ReadView

如果:

事务2还没提交。

ReadView看到:

trx_id = 2 是活跃事务

于是:

不能读。

于是:

往下找版本。

100 (trx_id=1)

这个版本:

已提交。

所以:

事务C看到的是100。


七、为什么MVCC可以提高性能

这个点面试官特别爱问。

传统锁模型:

读 -> 加锁
写 -> 加锁

读写冲突:

读被写阻塞
写被读阻塞

MVCC:

读 -> 不加锁
写 -> 生成新版本

所以:

读写不冲突

这叫:

Snapshot Read(快照读)


八、快照读 vs 当前读(面试必问)

快照读(MVCC)

例如:

select * from user;

读取:

历史版本

不加锁。


当前读(加锁)

例如:

select … for update
select … lock in share mode
update
delete
insert

读取:

最新版本

并加锁。


九、不同隔离级别下 ReadView 不同

这是 面试超高频细节

RC(Read Committed)

每次查询:

生成新的 ReadView

所以:

可以看到最新提交数据。


RR(Repeatable Read)

事务第一次查询:

生成 ReadView

之后:

一直复用

所以:

事务里数据 不会变化

这就是:

可重复读。