数据库如何通过 MVCC 实现隔离性

一、MVCC 是什么

MVCC 是一种"多版本 + 快照读"的并发控制机制,用来实现隔离性,并通过不同的 ReadView 策略支撑不同隔离级别,同时保证读写不阻塞。

二、MVCC的实现

1、隐藏字段(每行记录的"身份证")

InnoDB 在每行记录上,除了你定义的字段,还偷偷加了几个隐藏字段:

而与 MVCC 相关的字段只有前2项

隐藏字段 含义 作用
DB_TRX_ID 最后一次修改该行的事务 ID 判断版本可见性
DB_ROLL_PTR 回滚指针,指向 undo log 中的上一个版本 串联版本链

2、Undo Log版本链(历史版本仓库)

每次修改数据,旧值都会被写进 undo log ,并通过 DB_ROLL_PTR(回滚指针) 串成一条版本链。

特点:

  • 链首是最新版本。

  • 越往后越旧。

  • 每个版本都带 trx_id,用于可见性判断。

3、ReadView(可见性判断容器)

这里先说一下快照读 和当前读:

复制代码
快照读是什么?
  = 读历史版本(快照),不是读最新
  = 普通 SELECT
  = 不加锁
  = 靠 MVCC 实现(ReadView + undo log 版本链)

对比:
  快照读 → 读历史、不加锁、并发高
  当前读 → 读最新、加锁、保证强一致

ReadView(读视图)是事务在执行快照读时生成的一个对象,里面记录了"生成这一刻"数据库的活跃事务信息,用来判断版本链上的哪个版本对当前事务可见。

ReadView 的四个字段

字段 含义
m_ids 生成 ReadView 时,当前**活跃(未提交)**的事务 ID 列表
min_trx_id m_ids 中的最小值
max_trx_id 下一个将要分配的事务 ID(当前最大 +1)
creator_trx_id 创建该 ReadView 的事务 ID

4、可见性判断算法

规则①:自己改的,可见

事务 A 改了数据,虽然还没提交

但 A 自己当然要能看到自己的修改

→ creator_trx_id == trx_id → 可见

规则②:早于 ReadView 生成时刻的,可见

如果 trx_id < min_trx_id

说明这个事务在 ReadView 生成之前就已经"不在活跃列表"了

→ 意味着它早就提交了 → 可见

规则③:晚于 ReadView 生成时刻的,不可见

如果 trx_id >= max_trx_id

说明这个事务是在 ReadView 生成之后才开启的

→ 快照不该看到未来的事务 → 不可见

规则④:介于两者之间的,看是否活跃

在 min 和 max 之间的,可能是:

  • 已提交的

  • 还活跃的

怎么区分?看它是否在 m_ids 里:

在 m_ids → 当时还活跃 → 不可见

不在 m_ids → 当时已提交 → 可见

不可见怎么办

当前版本不可见

↓

沿 DB_ROLL_PTR(回滚指针) 找上一个版本

↓

再判断,直到找到可见版本

↓

若都不可见,说明该行对当前事务"不存在"

三、隔离级别与MVCC的关系

隔离级别 实现方式 用 MVCC 吗
READ UNCOMMITTED(读未提交) 直接读最新,不加过滤 ❌
READ COMMITTED(读已提交) MVCC(每次新建 ReadView) ✅
REPEATABLE READ(不可重复读) MVCC(复用 ReadView) ✅
SERIALIZABLE(串行化) 加锁,串行执行 ❌

四、MVCC 如何实现 RC (读未提交) 和 RR(不可重复读)

快照读时,会沿着版本链逐个判断,一旦某个版本满足当前 ReadView 的可见性规则,就读这个版本并返回。

MVCC 靠"同一个版本链 + 不同的 ReadView 生成时机",实现了 RC 和 RR 的差异。

维度 RC(读已提交) RR(可重复读)
ReadView 生成时机 每次 SELECT 都新建 事务首次 SELECT 生成,之后复用
快照范围 每次读都是"最新快照" 全程一个"固定快照"
能否读到别人提交 ✅ 能(提交后下次读就能看到) ❌ 不能(快照固定)
解决的问题 脏读 脏读 + 不可重复读

五、事务中的隔离性是如何保证的

事务的隔离性是由锁和 MVCC 实现的。

其中 MVCC 的意思是多版本并发控制 ,指维护一个数据的多个版本,使得读写操作没有冲突 。它的底层实现主要分为三个部分:隐藏字段、undo log 日志、ReadView 读视图。

隐藏字段 :在 MySQL 中给每个表都设置了隐藏字段,一个是 trx_id(事务 id),记录每一次操作的事务 id,是自增的;另一个是 roll_pointer(回滚指针),指向上一个版本的事务版本记录地址。

undo log :主要作用是记录回滚日志,存储老版本数据。在内部会形成一个版本链,在多个事务并行操作某一行记录时,记录不同事务修改数据的版本,通过 roll_pointer 指针形成一个链表。

ReadView :解决的是一个事务查询选择版本的问题,在内部定义了一些匹配规则和当前的一些事务 id,判断该访问哪个版本的数据。不同的隔离级别快照读是不一样的,最终的访问结果也不一样:

  • RC 隔离级别:每一次执行快照读时生成 ReadView。

  • RR 隔离级别:仅在事务中第一次执行快照读时生成 ReadView,后续复用。

相关推荐
这个DBA有点耶11 分钟前
信创场景下数据同步软件怎么选?对比开源CDC与原生闭环工具的结构性差异
数据库·mysql·架构
多客:duokelijie44 分钟前
代练订单系统选型思路:跨品类游戏接入、订单分层管理、自动结算逻辑拆解!
数据库·mysql·微信小程序·前端框架·uni-app·php·持续部署
PingCAP1 小时前
平凯星辰上榜!2026 年胡润全球独角兽企业
数据库
PingCAP1 小时前
轨道交通 AFC 系统升级,平凯 Loop 把跨部门协同变成可追溯闭环
数据库
java_logo2 小时前
Docker 部署 CloudBeaver:轻松搭建网页数据库管理平台
数据库·docker·容器·数据库管理·dbeaver·轩辕镜像·cloudbeaver
李白客3 小时前
AI 数据库落地 NL2SQL:准确率卡在四成,问题在表注释不在模型
数据库
FluxDB3 小时前
FluxDB vs DBX vs DBeaver:GPUI、Tauri、Eclipse 三种技术栈实测
数据库·mysql
杨航 AI4 小时前
JEV的用法
java·前端·数据库
数据库小学妹4 小时前
MySQL分页结果不稳定:唯一键兜底与游标分页
数据库·mysql·dba
风翼靓崽4 小时前
mysql数据备份/恢复
数据库·mysql