MVCC 怎么实现的

✅ 什么是 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_idroll_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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值