0. 前言:MVCC是MySQL高并发的灵魂

我们吃透了MySQL事务ACID与四大隔离级别,明确了:RC、RR两大主流隔离级别,依靠MVCC实现读写并发

如果没有MVCC,数据库读写操作会互相阻塞,读阻塞写、写阻塞读,高并发场景完全无法支撑业务流量。

绝大多数开发者只知道一句话:MVCC实现了无锁读、读写不冲突

但完全不懂底层核心:

多版本到底多的是什么版本?

版本链如何存储、如何回溯?

ReadView快照如何判断数据可见性?

为什么RC不可重复读、RR可重复读?根源在哪?

为什么RC每次查询都刷新快照,RR事务只生成一次快照?

今天彻底手撕MVCC全套底层原理,打通事务隔离、无锁并发、RC/RR差异的终极闭环,解决99%的事务并发疑惑。

1. MVCC核心定义与适用场景

1.1 什么是MVCC?

MVCC(Multi-Version Concurrency Control)多版本并发控制:InnoDB核心并发机制,通过存储数据的历史版本,让读操作读取历史快照、写操作操作最新数据,实现读写不阻塞、无锁并发读

1.2 MVCC适用范围(高频考点)

1. 仅作用于 InnoDB存储引擎,MyISAM不支持;

2. 仅作用于 RC、RR隔离级别

3. 仅针对 普通SELECT快照读,当前读不生效;

4. 串行化隔离级别会强制加锁,禁用MVCC。

1.3 MVCC核心价值

1. 读写并发不阻塞,极大提升数据库并发吞吐;

2. 依托数据多版本,实现事务隔离,解决脏读、不可重复读;

3. 避免大量行锁竞争、减少死锁概率。

2. MVCC三大底层支撑(前置核心)

MVCC不是独立机制,依托三大底层能力实现,前序章节全部铺垫过:

2.1 行隐藏字段(版本基石)

每条InnoDB行数据自带三个隐藏字段:

1. DB_TRX_ID:当前行最后修改的事务ID;

2. DB_ROLL_PTR:回滚指针,指向undo log上一个历史版本;

3. DB_ROW_ID:无主键时的默认聚簇索引ID。

2.2 undo log 回滚日志(版本存储)

记录数据修改前的历史镜像,事务更新数据时,先将旧数据存入undo log,通过回滚指针串联成数据版本链

undo log 不仅用于事务回滚,更是MVCC多版本的数据源

2.3 ReadView 读视图(可见性判断)

事务查询时生成的快照视图,是MVCC的核心规则器,用来判断:当前版本数据是否对当前事务可见

3. undo log版本链深度拆解(多版本来源)

很多人不懂:多版本到底从哪来?答案就是undo log版本链

每次数据更新,不会直接覆盖旧数据,而是将旧数据打包存入undo log,通过roll_ptr指针串联,形成一条从新到旧的数据版本链表

3.1 版本链生成流程

1. 初始数据:存在最新数据页,记录初始事务ID、空回滚指针;

2. 事务A修改数据:旧数据写入undo log,新数据更新事务ID,roll_ptr指向旧版本;

3. 事务B再次修改:再次备份旧数据,新数据指针指向A的版本;

4. 多次修改后,形成一条链式历史版本队列

3.2 版本链核心作用

当事务需要读取历史快照数据时,无需访问最新数据,直接沿着版本链回溯,找到符合当前事务可见性的历史版本,实现无锁读。

4. ReadView快照机制(MVCC核心规则)

ReadView是MVCC的大脑,所有数据可见性全部由ReadView规则判定。

ReadView包含四个核心成员变量(面试必考):

4.1 ReadView四大核心字段

1. m_ids:当前系统中活跃未提交事务ID集合

2.min_trx_id:m_ids中的最小事务ID;

3. max_trx_id:生成ReadView时系统下一个待分配事务ID;

4. creator_trx_id:当前生成ReadView的事务自身ID。

4.2 数据版本可见性判定规则(满分逻辑)

拿到行数据的 DB_TRX_ID,对照ReadView做三段式判断:

规则1:trx_id < min_trx_id

该数据版本在ReadView生成前已提交,可见

规则2:trx_id >= max_trx_id

该数据版本是ReadView生成后新开事务修改,不可见

规则3:min_trx_id <= trx_id < max_trx_id

判断trx_id是否在m_ids活跃集合中:

存在 → 事务未提交,不可见

不存在 → 事务已提交,可见

最终逻辑:不可见则沿着版本链向前回溯,直到找到可见版本,若无则无数据。

5. RC与RR隔离级别MVCC核心差异(全网最透彻)

RC和RR的所有差异,本质只有一句话:ReadView生成时机不同

这是不可重复读出现的唯一根源,也是MVCC最核心重难点。

5.1 ReadView生成策略对比

RC读已提交每一次SELECT查询,都会重新生成一个全新ReadView

RR可重复读整个事务只在第一次SELECT时生成一次ReadView,全程复用

5.2 为什么RC会出现不可重复读?

事务A开启,两次查询同一数据;

中间事务B修改并提交数据;

RC每次查询刷新快照,第二次查询生成新ReadView,可见B的已提交数据;

同一事务两次查询结果不一致,产生不可重复读

5.3 为什么RR可以解决不可重复读?

事务A首次查询生成ReadView,全程固定不变;

后续其他事务修改提交,不会改变当前快照;

事务A始终读取事务开启瞬间的历史快照数据

全程数据一致,彻底杜绝不可重复读

6. 快照读 vs 当前读(彻底分清MVCC使用场景)

很多人混淆:为什么有时候MVCC不生效?因为区分不了快照读和当前读。

6.1 快照读(走MVCC无锁)

普通SELECT查询,读取历史快照数据,无锁、并发高、走MVCC:

select * from table where id = 1;

特点:读写不阻塞、读取快照、性能高、存在数据延迟。

6.2 当前读(不走MVCC、加锁)

写操作、锁定读,强制读取最新数据,加行锁、不走快照、禁用MVCC:

insert / update / delete / select ... for update / lock in share mode

特点:读取最新数据、存在锁竞争、会阻塞、数据实时一致。

7. MVCC能否解决幻读?(终极标准答案)

面试压轴高频问题,全网最标准结论:

单纯依靠MVCC,无法彻底解决幻读;RR级别幻读是「MVCC快照隔离 + 临键锁」共同解决的。

7.1 MVCC的局限性

MVCC只能隔离已有数据的修改,无法隔离新增数据

其他事务新增的数据,不在当前事务快照范围内,会出现幽灵数据。

7.2 InnoDB RR解决幻读的真实方案

1. 快照读:依靠MVCC读取历史快照,业务层面几乎感知不到幻读;

2. 当前读:依靠间隙锁+临键锁锁定范围,禁止新增数据,彻底杜绝幻读。

8. 高频误区深度纠错

误区1:MVCC可以解决所有事务并发问题

错!MVCC只解决快照读的并发隔离,当前读依旧依赖锁机制,无法替代锁。

误区2:RR级别完全靠MVCC杜绝幻读

错!单纯MVCC无法防新增,必须配合临键锁、间隙锁才能解决幻读。

误区3:RC和RR的区别是锁粒度不同

错!核心区别是ReadView快照生成时机不同,和锁无直接关系。

9. 今日满分面试题库

Q1:什么是MVCC?核心作用是什么?

MVCC是InnoDB多版本并发控制机制,依托undo log版本链与ReadView快照实现。通过读取数据历史版本,实现普通SELECT快照读无锁并发,读写不阻塞,大幅提升数据库并发性能,同时实现RC、RR隔离级别的数据隔离。

Q2:MVCC的底层实现依赖什么?

依赖三项核心机制:行隐藏字段记录事务ID与回滚指针;undo log存储数据历史版本并串联版本链;ReadView快照机制实现数据可见性规则判定。

Q3:RC和RR的MVCC核心差异是什么?

RC隔离级别每次执行SELECT都会生成新的ReadView,能看到其他事务已提交的最新数据,因此存在不可重复读;RR隔离级别事务首次查询生成一次ReadView并全程复用,事务内数据快照固定,彻底解决不可重复读。

Q4:MVCC能否解决幻读?为什么?

单纯MVCC无法彻底解决幻读,MVCC只能隔离已有数据的修改,无法隔离新插入数据。InnoDB RR级别通过「MVCC快照隔离+临键锁/间隙锁范围锁定」,从查询和写入双向杜绝幻读问题。

Q5:快照读和当前读的区别?

快照读为普通SELECT查询,走MVCC读取历史数据、无锁、高性能、存在数据快照延迟;当前读为更新、删除、锁定查询,读取最新数据、加行锁、存在阻塞、数据实时一致,不走MVCC机制。

10. 今日总结

我们彻底吃透MySQL MVCC核心原理,完成事务并发隔离闭环:

1. 掌握MVCC定义、适用场景、核心价值;

2. 吃透隐藏字段、undo版本链、ReadView三大底层支撑;

3. 精通数据可见性判定规则,理解多版本筛选逻辑;

4. 彻底搞懂RC/RR隔离级别本质差异与不可重复读根源;

5. 分清快照读与当前读使用场景与机制区别;

6. 掌握MVCC对幻读的处理能力与局限性。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐