NiceOffer

八股文解析

MySQL MVCC 是怎么实现的?Read View 如何判断可见性?

MySQLMVCC八股文

一句话结论

MVCC 通过隐藏列(DB_TRX_ID/DB_ROLL_PTR)+ undo log 版本链 + Read View 一致性视图,实现读写不互斥的快照读。

面试标准答法

一、MVCC 要解决什么问题

MVCC(Multi-Version Concurrency Control,多版本并发控制)核心目标是:读不阻塞写,写不阻塞读。在 InnoDB 中,普通 SELECT 是快照读(Snapshot Read),不加锁;UPDATE/DELETE/INSERT 是当前读(Current Read),加锁。MVCC 让快照读通过版本链 + Read View 判断可见性,完全避开锁竞争。

二、三个核心机制

1. 隐藏列(Hidden Columns)

InnoDB 每行记录有三个隐藏字段(非用户定义):

隐藏列作用
DB_TRX_ID(6字节)最近一次修改(插入/更新/删除)该行的事务 ID
DB_ROLL_PTR(7字节)回滚指针,指向 undo log 中该行的前一版本
DB_ROW_ID(6字节,可选)无主键时自动生成的行 ID,有主键则不占

关键点:DELETE 不是物理删除,而是标记删除——在 DB_TRX_ID 记录删除事务 ID,并生成一个版本到 undo log。

2. undo log 版本链

每次 UPDATE/DELETE 都会将旧版本数据写入 undo log,DB_ROLL_PTR 串起所有历史版本,形成单向链表:

[最新版本] DB_TRX_ID=T3, DB_ROLL_PTR → [版本2] DB_TRX_ID=T2 → [版本1] DB_TRX_ID=T1 → NULL

注意:undo log 分两种——insert undo log(插入时生成,事务提交后可直接清理)和 update undo log(更新/删除时生成,需 MVCC 使用,不能立即清理)。

3. Read View(一致性视图)

Read View 是事务执行快照读时生成的活跃事务快照,包含四个关键字段:

字段含义
m_low_limit_id当前已分配的最大事务 ID + 1(即下一个事务 ID)
m_up_limit_id活跃事务列表中最小的事务 ID
m_ids生成 Read View 时所有活跃事务 ID 列表
m_creator_trx_id创建该 Read View 的事务自身 ID

三、可见性判断规则(核心中的核心)

遍历版本链,对每个版本执行以下判断:

if (DB_TRX_ID == m_creator_trx_id) 
    → 自己修改的,可见
else if (DB_TRX_ID < m_up_limit_id) 
    → 事务已提交,可见
else if (DB_TRX_ID >= m_low_limit_id) 
    → 在 Read View 生成之后才开始的事务,不可见
else if (DB_TRX_ID ∈ m_ids) 
    → 事务未提交,不可见
else 
    → 事务已提交(在 m_up_limit_id 和 m_low_limit_id 之间但不在活跃列表),可见

简化记忆比自己早的且已提交的可见,比自己晚的或未提交的不可见,自己改的永远可见

四、RC 与 RR 的差异

隔离级别Read View 生成时机效果
READ COMMITTED(RC)每次快照读都生成新 Read View能看到其他事务已提交的最新数据
REPEATABLE READ(RR)事务第一次快照读时生成,后续复用整个事务看到同一份快照,解决不可重复读

这就是为什么 RR 能避免不可重复读,而 RC 不能——本质是 Read View 的复用策略不同。

对比表格:MVCC vs 锁机制

维度MVCC 快照读悲观锁(SELECT FOR UPDATE)
并发度读写完全并行,无阻塞读读并行,读写/写写互斥
实现成本依赖 undo log,存储开销大依赖锁结构,内存开销小
一致性快照一致性,非实时实时一致性(当前读)
适用场景读多写少,报表查询资金操作、库存扣减等强一致场景
死锁风险有(需死锁检测)
隔离级别RC/RR 均支持所有级别

常见追问

追问回答要点
RR 下为什么没有幻读?RR 通过 MVCC 解决快照读的幻读;但当前读(SELECT FOR UPDATE)仍需 Gap Lock + Next-Key Lock 解决。MVCC 和 Next-Key Lock 是两套机制,配合使用。
undo log 什么时候清理?由 purge 线程异步清理。判断条件:版本链上所有版本都不再被任何活跃 Read View 引用(即所有版本对当前活跃事务都不可见)。长事务会导致 undo log 膨胀。
Read View 生成时活跃事务列表怎么维护?InnoDB 维护全局活跃事务链表(trx_sys->rw_trx_list),Read View 生成时拷贝该链表快照。注意:只包含读写事务,只读事务不参与。
为什么 RC 下每次读都要新生成 Read View?因为 RC 的语义是"读已提交的最新数据",如果复用旧 Read View,就读不到其他事务后续提交的数据,不符合 RC 定义。这是 SQL 标准的要求。

面试回答模板(30 秒版)

延伸准备

  1. undo log 物理结构:深入理解 undo log 的段(segment)、页(page)组织方式,以及 insert undo 和 update undo 在回收策略上的差异,能体现底层功底。
  1. 一致性读 vs 锁定读的源码路径row_search_mvcc() 函数的执行流程,以及 lock_clust_rec_read_check_and_lock() 如何与 MVCC 配合,能区分"资深"和"中级"。
  1. MVCC 与索引的关系:二级索引(Secondary Index)没有 DB_TRX_ID,InnoDB 如何通过回表 + 主键版本链判断二级索引记录的可见性?这涉及 row_sel_build_prev_row() 的细节逻辑。
  1. 分布式事务下的 MVCC 局限:XA 事务中 MVCC 如何与全局事务 ID 交互,以及为什么分布式场景下通常需要额外的一致性方案。

想系统备战大厂大模型/Agent 开发?NiceOffer 提供 SDE+LLM 双轨 1v1 陪跑,合同保底 40w 年薪,文末扫码咨询。