MVCC实现原理及其作用

这里写自定义目录标题

为什么要有MVCC?

从用户需求出发,数据库想要实现什么功能呢?事务想要实现隔离性,也就是如果多个事务同时运行,各个事务之间不能相互打扰(不能有脏读、不可重复读、幻读)。其实加锁可以简单粗暴的解决这个问题,但如果连读操作都频繁加锁,显然会严重影响效率,因此如何在不加锁的情况下解决多个事务的并发读问题,是多版本并发控制(MVCC)实现的出发点。

如何实现?

现在我们有很多个事务,它们面对着同一张表,表里有很多条数据。有的事务可能在修改某条记录,有的在查询,有的在删除。如何让每个事务只能看到自己应该看到的数据呢?

首先我们想到,应该给每个事务编号,并且这个编号和每一行数据有一个对应关系,这样才能控制某个事务只能看某些数据。因此,INNODB在每行记录中存放了一个隐藏字段trx_id即最近一次插入或更新这条记录的事务id。

然后,每个事务应该知道自己当前能看哪些事务的数据,这里就要区分事务的隔离级别了,读已提交 显然要比可重复读能看到的数据多一些。Mysql 通过Read View来记录创建一个事务时,系统中其他事务的相关信息,并以此来判断当前事务对某条数据的可见性。

Read View以及可重复读的隔离原理

顾名思义,Read View 就是个"读视图",或者说事务在创建时"拍了个快照"。如果类比java的数据结构的话,可以把它看做事务中的一部分,一个内部类。

java 复制代码
class ReadView {
	trx_id[] m_ids; //表示在生成readview时,当前系统中活跃的读写事务id数组; 
	trx_id min_trx_id; /*表示在生成readview时,当前系统中活跃的读写事务中最小的事务id,
	 					 也就是m_ids中最小的值,低水位; */
	trx_id max_trx_id; //表示生成readview时,系统中应该分配给下一个事务的id值,高水位; 
	trx_id creator_trx_id; //表示生成该readview的事务的事务id;
}

以上代码并不是真实的实现哈,只是为了方便理解,临时编写的一个类。

我们先以可重复读隔离级别为例,在Repeatable Read隔离级别下,Mysql将在启动事务时创建一个Read View,因此事务后续在访问数据记录时会有以下几种情况:

  • 如果记录的 trx_id 值等于Read View中的creator_trx_id,也就是说这条记录就是当前事务创建的,显然该记录可见;
  • 如果记录的 trx_id 值小于Read View中的min_trx_id,表示该版本的记录在本事务创建之前就有了,因此可见;
  • 如果记录的 trx_id 值大于Read View中的max_trx_id,表示该版本的记录在本事务创建之后才创建,因此不可见;
  • 如果记录的 trx_id 值介于min_trx_id和max_trx_id之间,就要进一步判断trx_id是否在数组m_ids中:
    • 如果trx_id在数组m_ids中,表明生成该记录的事务还没提交,处于活跃状态,因此该记录对当前事务不可见;
    • 如果trx_id不在数组m_ids中,表明生成该记录的事务已经被提交,因此该记录可见。

这时还有一个问题,就是某条数据因为事务状态的原因导致不可见,但是这条数据之前就存在了,也就是说当前事务应该看到这条数据之前的版本,这应该如何实现呢?

其实,在mysql的数据行中还有一个隐藏列,就是roll_pointer,每次事务对记录创建或修改时,都会将旧版本的数据计入undo日志,roll_pointer则负责以指针的形式连接这些不同版本的数据。当一个版本的trx_id不可见时,会顺着roll_pointer向以前的版本寻找,直到数据可见或没有数据。

读已提交是如何实现的?

上面描述了可重复读的工作原理,那读已提交是如何实现的呢?其实它俩的区别就一句话:

可重复读只在事务开始时创建一次Read View,读已提交会在每次读操作时创建Read View。

这就意味着,在可重复读隔离级别下,如果事务A在执行过程中,其他事务提交了数据,A相当于是完全不知道的,它将会读到事务开始时的数据;而在读已提交隔离级别下,每次读操作都会刷新Read View,里面的各项参数也随之被更新,其他事务提交的修改就可见了。

总结

本文主要介绍了MVCC的工作原理,以及数据中的隐藏列和Read View是如何配合的。需要注意的是,MVCC只解决了快照读的隔离问题(也就是普通的select语句),这对于读已提交隔离级别来说已经足够,因为当前读和快照读的表现是一样的。而对于可重复读隔离级别来说,当前读还会产生幻读和不可重复读现象,这需要加锁解决,该问题将在后续文章中阐述。

相关推荐
xixixin_12 分钟前
【uniapp】uni.setClipboardData 方法失效 bug 解决方案
java·前端·uni-app
工业互联网专业16 分钟前
基于springboot+vue的校园二手物品交易平台
java·vue.js·spring boot·毕业设计·源码·课程设计·校园二手物品交易平台
isfox23 分钟前
一文拆解 Java CAS:从原理到避坑全攻略
java
JPC客栈30 分钟前
LeetCode面试经典 150 题(Java题解)
java·leetcode·面试
HyperAI超神经40 分钟前
【vLLM 学习】Aqlm 示例
java·开发语言·数据库·人工智能·学习·教程·vllm
异常驯兽师40 分钟前
IntelliJ IDEA 项目导入后 Java 文件图标显示为红色小写 j 的解决方法
java·路径配置
纪元A梦43 分钟前
华为OD机试真题——数据分类(2025A卷:100分)Java/python/JavaScript/C++/C语言/GO六种最佳实现
java·javascript·c++·python·华为od·go·华为od机试题
爱的叹息1 小时前
关于 梯度下降算法、线性回归模型、梯度下降训练线性回归、线性回归的其他训练算法 以及 回归模型分类 的详细说明
人工智能·算法·回归·线性回归
常年游走在bug的边缘1 小时前
基于spring boot 集成 deepseek 流式输出 的vue3使用指南
java·spring boot·后端·ai
可乐^奶茶1 小时前
2026《数据结构》考研复习笔记三(C++高级教程)
数据结构·笔记·考研