当前位置: 首页 > article >正文

MVCC实现原理及其作用

这里写自定义目录标题

  • 为什么要有MVCC?
    • 如何实现?
    • Read View以及可重复读的隔离原理
    • 读已提交是如何实现的?
    • 总结

为什么要有MVCC?

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

如何实现?

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

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

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

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

顾名思义,Read View 就是个“读视图”,或者说事务在创建时“拍了个快照”。如果类比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记录存储示意图
其实,在mysql的数据行中还有一个隐藏列,就是roll_pointer,每次事务对记录创建或修改时,都会将旧版本的数据计入undo日志,roll_pointer则负责以指针的形式连接这些不同版本的数据。当一个版本的trx_id不可见时,会顺着roll_pointer向以前的版本寻找,直到数据可见或没有数据。

读已提交是如何实现的?

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

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

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

总结

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


http://www.kler.cn/a/471822.html

相关文章:

  • UI自动化测试保姆级教程--pytest详解(精简易懂)
  • LeetCode 第34题:二分查找+扩展搜索
  • IDEA 字符串拼接符号“+”位于下一行的前面,而不是当前行的末尾
  • Vue.js支持哪些数据可视化工具?
  • 【Unity3D】Text文本文字掉落效果
  • 【Cesium】自定义材质,添加带有方向的滚动路线
  • 《 小A点菜》
  • linux-27 发行版以及跟内核的关系
  • Word中所有的通配符使用方式[Word如何批量删除中文标点符号,英文标点符号,英文字母符号,数字符号,中文汉字符号]
  • homework 2025.01.07 math 6
  • 力扣904.水果成篮
  • 微信小程序广告变现收益低,从哪些方面优化广告策略?
  • 每日一题:链表中环的入口结点
  • CSS——15. 第一和最后子元素选择器
  • lec3-数的表示
  • LabVIEW无标题的模态VI窗口的白框怎么去除?
  • SQL从入门到实战
  • Mysql--基础篇--函数(字符串函数,日期函数,数值函数,聚合函数,自定义函数及与存储过程的区别等)
  • LeetCode热题100-相交链表【JavaScript讲解】
  • 解决高并发环境消息通知涉及问题
  • 李宏毅机器学习课程笔记02 | 机器学习任务攻略General Guide
  • 基于Qlearning强化学习的机器人迷宫路线搜索算法matlab仿真
  • MTK平台-- 无线AP隔离功能
  • FPGA设计:入行芯片领域的理想起点
  • C#中的关键字out和ref的区别
  • 低空管控技术-无人机云监视技术详解!