✅ 什么是 MVCC?它是怎么实现的?(适合基础不牢固者)
一、MVCC 是什么?
MVCC 全称是:Multi-Version Concurrency Control,中文叫:多版本并发控制。
主要用于解决数据库的读写并发冲突问题,它的作用是让读操作无需加锁,也能读到符合事务隔离要求的数据版本。
你可以理解成:
💬「数据库里的一行数据,可能同时有多个版本,读操作可以看到一个旧版本,写操作操作一个新版本,从而实现读写不冲突。」
二、为什么要有 MVCC?
我们先想一个问题:
多个线程同时读写数据,如果每次都加锁,会不会很慢?
是的!所以:
-
✅ 为了让“读”不加锁、快速返回
-
✅ 为了让“写”不影响“读”
➡️ 引入了 MVCC 技术!
三、MVCC 是如何实现的?(以 MySQL InnoDB 为例)
InnoDB 使用 MVCC 实现了“事务隔离”中的:读已提交(Read Committed)和可重复读(Repeatable Read)。
实现的关键:隐藏字段 + undo log + 事务ID
🌱 每行记录背后都有两个隐藏的版本号字段:
| 字段名 | 说明 |
|---|---|
trx_id | 当前修改这行记录的事务ID(记录这个版本是谁改的) |
roll_pointer | 指向 undo log(回滚日志)的指针,可以找到旧版本的值 |
🧩 举个真实场景:
假设数据库里有这样一行记录:
id = 1, name = "Alice", trx_id = 100
现在:
-
A 事务开启,看到的是
trx_id <= 100的数据版本 -
B 事务修改
name = "Alice" -> "Bob",这时候新数据行trx_id = 101,旧数据被记录在 undo log 里
A 事务继续查询这个 id=1 的数据时,会读取旧版本(name = "Alice"),因为它的事务ID比 101 小,看不到未提交的新值。
这样,读就不需要锁,只要读取对应版本即可。
四、MVCC 依赖哪三大核心组件?
| 名称 | 作用 |
|---|---|
| 1️⃣ Undo Log | 保存旧版本记录(回滚日志) |
| 2️⃣ Read View | 是事务执行时生成的一份“可见性快照”,它决定当前事务能看到哪些版本的数据 |
| 3️⃣ 隐藏字段 | 每行记录中的 trx_id 和 roll_pointer 实现版本控制 |
🧠什么是 Read View?(关键组件)
Read View 是事务执行时生成的一份“可见性快照”,它决定当前事务能看到哪些版本的数据。
它由以下内容组成:
| 名称 | 说明 |
|---|---|
min_trx_id | 所有活跃事务中,最小的事务 ID |
trx_ids[] | 当前系统中活跃事务的 ID 列表(正在运行但未提交) |
trx_id | 当前事务自己的 ID |
📌 Read View 判断可见性的 3 个规则(重要)
假设你事务 ID 是 trx_id = 100,活跃事务列表是 [95, 96, 97]:
判断某条记录的版本 rec_trx_id 是否可见,有以下规则:
| 条件 | 判断 | 说明 |
|---|---|---|
| rec_trx_id < min_trx_id | ✅ 可见 | 修改者比所有活跃事务都早,肯定是已提交的 |
| rec_trx_id ∈ trx_ids[] | ❌ 不可见 | 修改者是其他正在进行中的事务,未提交 |
| rec_trx_id > 当前事务ID | ❌ 不可见 | 说明是后开启的事务,当然也不能看 |
📖 示例题:读快照怎么判断可见性?
假设有如下事务:
-
T1:trx_id=100,事务启动时其他活跃事务为:101,102
-
你看到一条记录是:trx_id=102 修改的
你能看到它吗?
✅ 答案:不能。因为它是一个未提交的活跃事务(在 Read View 的活跃事务列表中)
🧾 undo log 是什么?长什么样?
每次对数据的更新、删除操作,InnoDB 都会在 undo log 中记录下旧值。
举个例子:
你更新一条记录:
UPDATE user SET name = 'Bob' WHERE id = 1;
Undo Log 会记录:
name = 'Alice'
trx_id = 101
这样一来,如果另一个事务仍然在使用旧版本数据,它可以通过 roll_pointer 找到 undo log 拿到 Alice 的版本。
🧪 MVCC 和锁的配合(面试必考)
🚫 MVCC 不能解决的场景:
-
写写冲突
-
幻读(RR 通过间隙锁处理)
-
非 MVCC 支持的操作,如
select ... for update
| 场景 | 是否使用 MVCC | 是否加锁 |
|---|---|---|
| 普通 select | ✅ 是 | ❌ 否 |
| select ... for update | ❌ 否 | ✅ 加行锁 |
| update/delete | ❌ 否 | ✅ 加锁(锁记录 + 写 undo log) |
五、MVCC 实现了哪些隔离级别?
| 隔离级别 | 是否使用 MVCC | 是否加锁 | 说明 |
|---|---|---|---|
| Read Uncommitted(读未提交) | ❌ 不使用 | ❌ 不加锁 | 能读到其他事务未提交的数据,不安全,MVCC 无法处理这种 |
| Read Committed(读已提交) | ✅ 使用 | ❌ 不加锁 | 每次读都生成新快照,只读已提交数据 |
| Repeatable Read(可重复读) | ✅ 使用 | ❌ 不加锁 | 事务开始时生成快照,事务内多次读同一数据一致 |
| Serializable(可串行化) | ❌ 不使用 | ✅ 加锁 | 最严格,MVCC 无法保证,需要强制加锁实现 |
🎯 表现解读说明:
-
Read Uncommitted
-
最低级别,事务可以看到其他事务未提交的修改,导致脏读、不可重复读、幻读全部可能出现。
-
-
Read Committed(Oracle 默认)
-
避免了脏读,但事务内每次查询都是当前已提交版本,仍可能出现不可重复读和幻读。
-
MySQL 中使用 MVCC 快照版本来实现。
-
-
Repeatable Read(MySQL 默认)
-
MVCC 快照保证同一事务中多次读取结果一致(避免不可重复读)。
-
幻读则通过“间隙锁 Gap Lock”机制来防止(锁住可能被插入的范围),所以能避免幻读。
-
-
Serializable(最严格)
-
强制事务串行执行,使用全表锁避免所有并发问题(代价性能较高)
-
🧠 记忆口诀:
未提交不安全(MVCC不管),已提交每次新快照,可重复一视同仁串行靠加锁。
🎯 面试答题模板(可直接背):
在 InnoDB 中,MVCC 用于实现 RC 和 RR 两个隔离级别。
在 RC 隔离级别下,每次读操作生成新的 Read View,只能读到已提交数据;
在 RR 隔离级别下,事务启动时生成一次快照,整个事务期间都使用这个快照,实现可重复读。
RU 和 Serializable 不使用 MVCC,前者可能出现脏读,后者需要加锁来保证串行一致性。
🔍 小补充:InnoDB 默认隔离级别是?
✅ Repeatable Read(可重复读)
✅ 所以:默认就是使用 MVCC 的!
六、一句话记住 MVCC 面试回答模板
MVCC 通过为每行数据保存多个版本(trx_id 和 undo log),结合当前事务的 Read View 快照,实现了读写分离、读不加锁,从而提升了数据库并发性能。在 MySQL 的 InnoDB 引擎中主要支持 RC 和 RR 隔离级别。
七、图示理解(简化版)
┌────────────┐
│ 事务 A │ trx_id = 100
└────────────┘
↓
读取记录 id=1
↓
┌─────────────────────┐
│ 当前记录: name = Bob │ trx_id = 101
│ UndoLog: name = Alice│ trx_id = 100
└─────────────────────┘
A 事务只能看到旧值:Alice

4万+

被折叠的 条评论
为什么被折叠?



