MySQL(四)—MVCC实现可重复读的原理

article/2025/9/28 10:19:49

文章目录

  • 一、MVCC概况
  • 二、MVCC实现原理
    • 1.两或三个隐藏字段。
    • 2.undo log
    • 3.一个数组
    • 4.ReadView
  • 三、举例验证MVCC原理
    • 参考文献

一、MVCC概况

MVCC是什么?MVCC即多版本控制协议,InnoDB实现了MVCC作版本控制,防止不该被当前事务看到的数据看到。
举个例子,下面就是在T4时刻,事务A和事务C看到的数据不一致,也就是说有多个版本。

事务时刻事务A事务B事务C
T1begin;begin;begin;
T2select * from transation_test;

结果:id salary
1 1 | | |
| T3 | | 插入一行数据(2,2)
commit; | |
| T4 | select * from transation_test;
结果:id salary
1 1 ** ** | | select * from transation_test;
结果:id salary
1 1
2 2 |

引入MVCC的原因就是在读写事务时读的情况不加锁,提高并发性能。

二、MVCC实现原理

本质理解
在InnoDB中,主要是通过使用readview的技术来判断数据是否能当前事务读到。如果可以,则输出,否则就利用undolog来构建历史版本,再进行判断,直到记录构建到最老的版本或者可见性条件满足。

上面的工作需要两或三个隐藏字段、undo log、一个数组、ReadView完成。

1.两或三个隐藏字段。

InnoDB表数据组织方式是聚簇索引。在聚簇索引上还有一些额外信息会存储,就是两或三个隐藏字段。
DB_TRX_ID:事务ID,表示最近一次插入或者更新该记录的事务ID。
DB_ROLL_PTR:回滚指针,指向该记录的之前的undo log记录
DB_ROW_ID:当表上没有用户主键的时候,InnoDB会自动创建(这也是为什么两或三个隐藏字段)

假设一个表中有两个字段(ID,Name),一个事务id为1插入一条记录(1,事务1)。该条记录还没有上一版本,回滚指针为null。那么现在这张表就变成了下面的样子。
在这里插入图片描述

2.undo log

前面我们也介绍过undo log在innodb中有两个作用,MVCC、事务回滚。undo log主要存放一条sql语句执行前的记录及与sql语句相反的操作。

现在来一个事务要修改ID为1这一行记录,那么它的过程如下。

首先向InnoDB申请一个事务ID,注意事务ID是严格递增的,假如申请的ID为3,简称事务3。
事务3要对ID为1的记录做update修改操作,数据库为这行记录加上行锁。
将该行数据拷贝进undo log中
拷贝完成后,事务3将ID为1记录修改为(1,事务3),事务ID也会变成3,回滚指针指向undo log中该条记录事务1版本
事务3提交,释放锁。

在这里插入图片描述

现在又来一个事务,修改ID为1这行记录
该事务会重新走一遍事务3的流程,假如该事务ID为5,则现在的图变为
在这里插入图片描述

可以看出此时事务对同一条记录的修改,会使这条记录的undo log变为一个链表的形式。链首是最新的旧记录,链尾是最早的旧记录。

注意点
undo log分为insert undo和update undo,insert undo即执行insert 操作留下的历史记录,insert undo会在事务提交/回滚后直接删除,而update undo会保留下来做历史版本链表。上面为了能够讲述明白所以没有删除事务1的insert undo

3.一个数组

在InnoDB内部维护着一个数组,该数组(trx_sys->descriptors数组)会记录当前还未提交的事务id,id会从小到大排序。也就是说事务执行时会向InnoDB申请一个事务id,该数组会记录此id,如果该事务提交了,则从该数组中删除。

这个数组有什么用呢?为下面的ReadView做铺垫,创建ReadView时会复制一份该数组到ReadView中,ReadView会依据数组中未提交的id值进行判断事务是否可见一条记录。

4.ReadView

什么是ReadView?
ReadView,读视图。ReadView从代码层面其实是一个结构体(C语言名词),名叫read_view_t。事务其实也是一个结构体,trx_t。每个数据库连接持有一个trx_t(事务),每个trx_t(事务)持有一个read_view_t(读视图);事务进行快照读操作产生的一个ReadView。

ReadView有什么用?
前面说到ReadView主要做事务可见性判断,即某个事务执行快照读时,对该记录创建一个ReadView读视图,根据ReadView去判断当前事务能够看到哪个版本数据,有可能是最新的数据,也有可能是该行记录undo log里面某个版本的数据。

ReadView如何做可见性判断?
回答这个问题要从ReadView结构体中的属性入手了。
read_view_t

  • descriptors数组(readview数组):拷贝记录当前活跃事务id的trx_sys->descriptors到该数组中
  • up_limit_id:记录该数组中的最小值(min_trx_id有点反人类,up对应min)
  • low_limit_id:记录系统还未分配给事务的id,该值大于descriptors数组中的最大值(因为还没分配给事务的id是创建readview时刻当前系统中最大的,InnoDB从小到大给事务分配)。

这几个属性有什么用呢?利用上述属性做事务可见性判断

判断的核心思想是事务启动以前及以后所有还没提交的事务,它都不可见。源码如下

//id:一条记录的事务id
bool changes_visible(trx_id_t id, const table_name_t &name) constMY_ATTRIBUTE((warn_unused_result)) {ut_ad(id > 0);//如果这条记录的事务id<数组中最小值 或者 等于当前事务id,返回true那么当前事务可见这条记录if (id < m_up_limit_id || id == m_creator_trx_id) {return (true);}check_trx_id_sanity(id, name);//如果这条记录的事务id大于等于最大事务id,返回false那么当前事务不可见这条记录if (id >= m_low_limit_id) {return (false);} else if (m_ids.empty()) {return (true);}const ids_t::value_type *p = m_ids.data();return (!std::binary_search(p, p + m_ids.size(), id));
}

为了方便理解,下面对上面的代码做进一步的说明,一共分四种情况

如果最新记录上事务id<up_limit_id(min_trx_id),证明当前事务构建readview时这个事务已经提交了,所以可以看见这条记录

如果最新记录上事务id>=low_limit_id(max_trx_id),证明当前事务构建readview时这个事务还没有对记录进行修改操作,所以看不见这条记录

如果最新记录上事务id在up_limit_id和low_limit_id之间,且在readview数组中,证明当前事务构建readview时这个事务正在修改该条记录,所以看不见这条记录

如果最新记录上事务id在up_limit_id和low_limit_id之间,且不在readview数组中,证明当前事务构建readview时这个事务已经提交,所以可以看见这条记录

是不是字太多,记这么多东西简直是难为人。

总结下最核心的,InnoDB的事务快照读的情况下只能看见已经提交事务的数据,已经提交分为两种情况

  • 一条记录事务id<up_limit_id,证明当前事务构建readview时这个事务已经提交了,所以可以看见这条记录
  • 一条记录事务id在readview数组范围中,但不在readview数组中,也可以证明事务已经提交了

如果满足其中一种情况,事务则可以看见该记录

三、举例验证MVCC原理

事务隔离级别为可重复读。当前系统中有5个事务,5个事务都对id为1这行记录进行操作。
其中事务1和事务5已经提交,事务8进行快照读。

时刻事务1事务3事务5事务7事务8
T1begin;begin;begin;begin;begin;
T2插入(1,事务1)记录
commit;
T3修改id为1的名字为事务3修改id为1的名字为事务5
commit;修改id为1的名字为事务7;
查询id为1的记录(快照读)

事务8在T4时刻快照读创建ReadView,在T4时刻可以读取到事务几的数据呢?

首先看T4时刻ReadView中各个属性的值为多少

  • ReadView数组:拷贝全局未提交事务id,即[3,7,8]
  • up_limit_id:记录readview数组中最小值为3
  • low_limit_id:记录系统尚未分配的事务id为9

根据前面所说隐藏字段及undo log版本链,可以做成如下图
在这里插入图片描述

最后得出结论,事务8可以读取到事务5版本的数据

需要注意的是:
MVCC可以通过ReadView的方式实现读已提交和可重复读的隔离级别,但是两种隔离级别创建的ReadView的时间点不同。

  • 读已提交会在每次Select创建一个ReadView
  • 可重复读是第一次select之后创建ReadView,之后再select都会复用。

因此可重复读的隔离级别解决了不可重复读的问题,并一定程度上避免了幻读问题,但是没有真正结解决,请看下一篇


参考文献

事务实现源码级:http://mysql.taobao.org/monthly/2018/11/04/
事务概括:http://mysql.taobao.org/monthly/2017/12/01/
通俗易懂级MySQL MVCC
深入理解MVCC:https://www.cnblogs.com/kismetv/p/10331633.html


http://chatgpt.dhexx.cn/article/4RqVacIB.shtml

相关文章

可重复读

《循序渐进DB2-系统管理、运行维护与应用案例》第10章锁和并发&#xff0c;本章首先介绍了通用的事务概念&#xff0c;并指出DB2在用户读取、写入数据时的加锁策略、锁模式、兼容性等。本章还介绍了在并发控制中常碰到的4种数据异常现象&#xff0c;同时讲述了DB2如何使用锁克服…

mysql不可重复读和重复读_脏读、幻读、不可重复读的区别是什么

脏读、幻读、不可重复读的区别&#xff1a;1、脏读就是指当一个事务正在访问数据&#xff0c;并且对数据进行了修改&#xff1b;2、不可重复读是指在一个事务内&#xff0c;多次读同一数据&#xff1b;3、幻读是指当事务不是独立执行时发生的一种现象。 【相关学习推荐&#xf…

Mysql如何实现可重复读

首先对于mysql来说相信也并不陌生&#xff0c;mysql默认的事务的隔离级别是3&#xff0c;即可以实现可重复读&#xff0c;那mysql又是怎样实现可重复读的呢&#xff1f; 下边进行简单的介绍 这里就要提到了mvcc&#xff0c;即多版本并发控制 首先先来看一个事务的执行图 此时…

MySQL 可重复读隔离级别,完全解决幻读了吗?

我在上一篇文章中提到&#xff0c;MySQL InnoDB 引擎的默认隔离级别虽然是「可重复读」&#xff0c;但是它很大程度上避免幻读现象&#xff08;并不是完全解决了&#xff09;&#xff0c;解决的方案有两种&#xff1a; 针对快照读&#xff08;普通 select 语句&#xff09;&am…

MySQL可重复读-问题实践

MySQL可重复读之幻读问题 MySQL事务存储引擎InnoDB的默认隔离级别为可重复读&#xff0c;在该隔离级别下&#xff0c;可以很大程度上避免幻读的问题&#xff0c;完全避免脏读和不可重复读问题&#xff0c;接下来通过实际的测试看看这些场景是否真的能够被完全避免 模拟均基于…

可重复读下的幻读

在事务执行过程中&#xff0c;另一个事务将新记录添加到正在读取的事务中时&#xff0c;会发生幻读&#xff08;事务隔离级别为可重复读REPEATABLE-READ&#xff09;。当执行SELECT … WHERE语句时未对where范围锁定&#xff0c;则可能会发生幻读。幻读是不可重复读的一种特殊情…

MySQL可重复读应用场景_mysql-repeatable read 可重复读隔离级别-幻读实例场景

本文详解 repeatable read 可重复读 隔离级别产生的影响(幻读) -- SERIALIZABLE serializable 序列化 &#xff1b;一个个事务排成序列的形式。事务一个挨一个执行&#xff0c;等待前一个事务执行完&#xff0c;后面的事务才可以顺序执行 -- REPEATEABLE READ repeatable read …

MySQL可重复读级别能够解决幻读吗

引言 之前在深入了解数据库理论的时候&#xff0c;了解到事物的不同隔离级别可能存在的问题。为了更好的理解所以在MySQL数据库中测试复现这些问题。关于脏读和不可重复读在相应的隔离级别下都很容易的复现了。但是对于幻读&#xff0c;我发现在可重复读的隔离级别下没有出现&…

mysql不可重复读和重复读_MySql隔离级别:RU / RC / RR / S + 脏读 / 不可重复读 / 幻读 / 可重复读...

MySQL 事务 本文所说的 MySQL 事务都是指在 InnoDB 引擎下&#xff0c;MyISAM 引擎是不支持事务的。 数据库事务指的是一组数据操作&#xff0c;事务内的操作要么就是全部成功&#xff0c;要么就是全部失败&#xff0c;什么都不做&#xff0c;其实不是没做&#xff0c;是可能做…

mysql可重复读 加锁_mysql可重复读隔离级别加锁分析

问题 myql可重复读隔离级别下可能会导致插入阻塞&#xff0c;问题复现如下 表中有3列都是int类型 其索引情况如下&#xff1a; id为主索引&#xff0c;c,d为普通索引 现在开始制作问题&#xff1a; 在这里我分别开启两个事务&#xff1a;第一个事务中执行一个update 语句更新一…

可重复读实现原理

不可重复读&#xff1a;事务A多次读取同一个数据&#xff0c;事务B在事务A多次读取的过程中&#xff0c;对数据作了更新&#xff0c;导致事务A多次读取同一个数据时&#xff0c;结果不一致。&#xff08;比如修改行数据&#xff09; 幻读&#xff1a;事务A 按照一定条件进行数据…

mysql “可重复读“ 解决了哪些问题,没有解决哪些问题?

可重复读解决了更新带来的不可重复读问题&#xff0c;但是没有解决插入或者删除带来的幻读问题。这句话&#xff0c;是老八股文了。 但真实情况是这样的吗&#xff1f;这个验证不麻烦&#xff0c;我们可以动手来验证一下。 我使用的是免安装版本的windows mysql8.0.31&#xff…

透彻解读mysql的可重复读、幻读及实现原理

目录 一、事务的隔离级别 二、mysql怎么实现的可重复读 举例说明MVCC的实现 MVCC逻辑流程-插入 MVCC逻辑流程-删除 MVCC逻辑流程-修改 MVCC逻辑流程-查询 三、幻读 快照读和当前读 四、如何解决幻读 事务隔离级别有四种&#xff0c;mysql默认使用的是可重复读&#x…

前端开发:a标签实现下载功能

应用背景 前端项目实现下载文件的功能&#xff0c;在后台没给我们撸接口的情况下&#xff0c;我们可以利用a标签实现下载功能&#xff0c;而且贼简单~ 实现原理 通过a标签的download的属性&#xff0c;将需要下载的文件放在前端项目中&#xff0c;然后href属性访问文件路径&a…

a标签/js下载文件(2020)

a标签/js 下载服务器文件 一、二进制式下载1、responseType&#xff08;请求&#xff09;2、Content-Type&#xff08;响应&#xff09;判断是普通数据还是文件流&#xff08;可选&#xff09;3、Content-Disposition&#xff08;响应&#xff09;和文件名&#xff08;可选&…

a标签下载pdf文件

通过a标签的download属性可以实现下载pdf文件&#xff0c;不过有一个弊端&#xff1a;网站和pdf文件必须在同一域名下才可行&#xff0c;不然就是先打开一个新标签预览&#xff0c;然后点击下载按钮进行下载。

a标签实现文件下载功能

文件下载原理&#xff1a; java后台只能做到返回二进制流或文件给前端&#xff0c;最终在前端页面创建一个a&#xff0c;然后触发a的点击事件实现点击下载效果。 1.无需token的 2.请求头需要token 接口&#xff1a; 点击事件:

前端-基于a标签实现下载功能

最近在一个项目中需要实现下载功能&#xff0c;在前期与后端多次联调尝试使用接口下载文件无果后&#xff0c;最后抱着试一试的心态使用了A标签下载&#xff0c;方法是有效的&#xff0c;但是有部分局限性&#xff01;&#xff01;&#xff01; 使用a标签实现下载的步骤如下&am…

a标签的download属性(荐)

在html 中 a 链接有 download 这样一个属性 它有什么用呢&#xff1f;&#xff01; 我们在页面中提供下载的时候&#xff0c;都需要去配置一些服务端的东西&#xff0c;比如指定 zip 文件就通知浏览器下载这个文件。 但是&#xff0c;比如 .jpg 这样的图片文件&#xff0c;如…

html a标签下载文件

<a href"/user/test/xxxx.txt" download"文件名.txt">点击下载</a>