MySQL-MVCC核心原理-版本链ReadView与可见性判断

MVCC 全称是 Multi-Version Concurrency Control,也就是多版本并发控制。它的核心思想是:为同一行数据维护多个版本,让读写在很多情况下不用互相阻塞。

没有 MVCC 时,读写冲突通常要大量依赖锁。MVCC 让普通 select 可以读一个可见的数据版本,而不是必须等待正在修改这行数据的事务结束。

MVCC 依赖哪三样东西

InnoDB 的 MVCC 主要依赖:

  1. 记录中的隐藏字段。
  2. undo log 版本链。
  3. ReadView 读视图。

这三者配合,解决一个问题:当前事务应该看到这行数据的哪个版本?

记录中的隐藏字段

InnoDB 每行记录除了业务字段,还会维护一些隐藏字段。

隐藏字段 含义
DB_TRX_ID 最近一次修改这行记录的事务 ID
DB_ROLL_PTR 回滚指针,指向 undo log 中的上一个版本
DB_ROW_ID 隐藏行 ID,没有主键时可能使用

其中 DB_TRX_IDDB_ROLL_PTR 是理解 MVCC 的关键。

undo log 版本链

当一行数据被多个事务修改时,undo log 会形成一条版本链。链表头部通常靠近较新的旧版本,沿着 roll_pointer 可以找到更早的版本。

例如一条记录从 A1 被改成 A2,又被改成 A3。当前数据页里是最新值,而 undo log 中保存旧值。不同事务根据可见性规则,可能读到 A3A2A1

当前读和快照读

不是所有读都走 MVCC 快照。

类型 SQL 示例 特点
当前读 select ... for updateupdatedeleteinsert 读取最新版本,并加锁
快照读 普通 select 读取可见版本,不加锁,非阻塞

MVCC 主要服务于快照读。普通 select 不加锁也能在并发写入时稳定读取,就是因为它读的是某个可见版本。

ReadView 是什么

ReadView 是快照读执行时生成的读视图,用来判断版本链上的某个版本是否对当前事务可见。

ReadView 中有四个核心字段:

字段 含义
creator_trx_id 创建这个 ReadView 的事务 ID
m_ids 创建 ReadView 时活跃且未提交的事务 ID 集合
min_trx_id 活跃事务中的最小 ID
max_trx_id 下一个将要分配的事务 ID

版本可见性规则

当沿着版本链找到某个版本时,会拿这个版本的 trx_id 和 ReadView 比较。

这块最适合按流程走。每遇到一个版本,就问几个问题:








快照读开始
生成或复用 ReadView
读取当前记录版本
trx_id 是否等于 creator_trx_id
版本可见
trx_id 是否小于 min_trx_id
trx_id 是否大于等于 max_trx_id
版本不可见
trx_id 是否在 m_ids 中
沿 undo log 版本链找上一个版本

规则可以简化为:

  1. trx_id == creator_trx_id:自己改的,能看见。
  2. trx_id < min_trx_id:这个版本在 ReadView 生成前已经提交,能看见。
  3. trx_id >= max_trx_id:这个版本在 ReadView 生成后才出现,不能看见。
  4. min_trx_id <= trx_id < max_trx_id:如果 trx_id 不在 m_ids 中,说明已提交,能看见;如果在 m_ids 中,说明当时还活跃,不能看见。

这些规则的目的很朴素:只让当前事务看到它应该看到的已提交版本。

RC 和 RR 的区别

READ COMMITTEDREPEATABLE READ 的核心差异之一,是生成 ReadView 的时机不同。

隔离级别 ReadView 生成时机 结果
READ COMMITTED 每次执行快照读都生成新的 ReadView 同一事务内多次查询可能看到新提交数据
REPEATABLE READ 第一次快照读生成 ReadView,后续复用 同一事务内多次查询结果更稳定

两种隔离级别的差别,可以这样看:
RC
RR
同一个事务内多次普通 select
隔离级别
每次 select 生成新的 ReadView
可能看到其他事务新提交的数据
第一次 select 生成 ReadView
后续 select 复用同一个 ReadView
同一事务内读取结果更稳定

这也解释了为什么 RC 下可能出现不可重复读,而 RR 能让普通快照读保持可重复。

一个简单例子

假设事务 5 创建了 ReadView:

text 复制代码
m_ids = {3, 4, 5}
min_trx_id = 3
max_trx_id = 6
creator_trx_id = 5

现在沿着版本链看到几个版本:

版本 trx_id 是否可见 原因
5 可见 当前事务自己修改
2 可见 小于 min_trx_id,早已提交
6 不可见 不小于 max_trx_id,属于更晚事务
4 不可见 m_ids 中,当时未提交

如果某个版本不可见,就继续沿着 undo log 版本链向前找,直到找到可见版本或没有更旧版本。

面试回答模板

可以这样回答:

MVCC 是多版本并发控制,用来降低读写冲突。InnoDB 每行记录有隐藏字段,比如事务 ID 和回滚指针;更新数据时会生成 undo log,多个旧版本通过回滚指针形成版本链。普通 select 是快照读,会基于 ReadView 判断版本是否可见。ReadView 中包含当前活跃事务集合、最小活跃事务 ID、下一个事务 ID 和创建者事务 ID。RC 隔离级别下每次快照读都会生成新的 ReadView,RR 隔离级别下第一次快照读生成后会复用,所以 RR 下多次读取更稳定。

小结

MVCC 的难点不是名词多,而是要把它们串成一条线:隐藏字段记录版本信息,undo log 串起旧版本,ReadView 决定哪个版本可见。理解这条线,MVCC 就从"背概念"变成了"顺着链表找可见数据"。

相关推荐
KaMeidebaby5 小时前
卡梅德生物技术快报|骆驼纳米抗体:从原核表达、高通量测序到分子对接全流程实现
前端·数据库·其他·百度·新浪微博
2301_783848658 小时前
优化文本分类中堆叠模型的网格搜索性能:避免训练卡顿的实战指南
jvm·数据库·python
拾贰_C8 小时前
【Ubuntu | 公共工作站 | mysql 】 MySQL残留物残留数据
linux·mysql·ubuntu
TE-茶叶蛋8 小时前
DBeaver 的Explain 执行计划,分析sql的性能
数据库·sql
CLX05058 小时前
如何安装Oracle 12c Cloud Control_OMS服务端组件与Agent部署
jvm·数据库·python
m0_617493949 小时前
PySide6 网络请求深度实测:从基础 API 调用到数据解析实战指南
数据库
知识汲取者9 小时前
每日一篇高频面试题系列之【MySQL 锁】
数据库·mysql
老纪9 小时前
SQL中如何查找特定的空值行:WHERE IS NULL深度解析
jvm·数据库·python
麦聪聊数据9 小时前
数据 API 平台选型:深度解读数据服务的四大关键技术与架构底座
数据库·sql