数据库如何通过 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,后续复用。

相关推荐
yeflx1 小时前
Ubuntu22.04安装Mysql Workbench
数据库·mysql·adb
PaperData1 小时前
1986-2025年《中国能源统计年鉴》全年份pdf+excel版
数据库·能源
夜雪一千1 小时前
MySQL 自增 ID 的底层原理与坑点
android·mysql·adb
SelectDB技术团队1 小时前
日志场景 Elasticsearch 替换实操:建表、索引、写入链路与查询改写的完整参数
大数据·数据库·数据仓库·elasticsearch·全文检索·日志分析·apache doris
百数平台1 小时前
低代码安全技术实践:表单 / 流程 / 报表三级权限体系设计解析
java·大数据·数据库
驭渊的小故事2 小时前
MyBatis-Plus 实战指南:从基础 CRUD 到条件构造器与自定义 SQL(二)
数据库·sql·mybatis
小小AK2 小时前
销售订单从 CRM 推到 ERP:一条策略的完整同步实战
数据库
木雷坞2 小时前
Docker Compose部署Docmost:数据库、附件持久化与首次协作
数据库·docker·容器
VidDown2 小时前
上线三个月后的第一次事故:一个 Django 服务的日志、监控与故障复盘
数据库·python·django·wpf·音视频·视频编解码