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语句),这对于读已提交隔离级别来说已经足够,因为当前读和快照读的表现是一样的。而对于可重复读隔离级别来说,当前读还会产生幻读和不可重复读现象,这需要加锁解决,该问题将在后续文章中阐述。

相关推荐
翼龙云_cloud12 小时前
腾讯云国际代理商:CDB数据库自动备份和异地灾备配置 从快照到跨区域恢复
运维·数据库·云计算·腾讯云
小屁孩你滑稽掉了12 小时前
prisma操作数据库的方法使用教程(简洁版)
数据库·node.js·prisma·fastify
大熊背12 小时前
树莓派相机自动白平衡详解(二)
算法·白平衡·isppipeline
Code额12 小时前
Python 连接 DeepSeek API,OpenAI 对话方式总结
后端·python·ai·ai编程
星火102412 小时前
【LangChain4j系列10】Guardrails 安全护栏
人工智能·后端
四千岁12 小时前
稀疏向量BM25Retriever不支持中文怎么办?jieba来帮忙
前端·javascript·后端
用户69190268133912 小时前
Docker基本概念
后端·docker·容器
颜进强12 小时前
14 - OpenSpec 老页面改造骨架:定位 + 增量 + 回归三件套
前端·后端·ai编程
唐青枫12 小时前
别只把 switch 当成多路 if:Zig 模式匹配、状态机与 Tagged Union 实战
后端
Scabbards_12 小时前
面试Leetcode - 算法合集
算法·leetcode·面试