MySQL多版本并发控制MVCC

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
日志服务 SLS,月写入数据量 50GB 1个月
云数据库 RDS MySQL,高可用系列 2核4GB
简介: MySQL多版本并发控制MVCC

引言


mysql 在我们平常项目中是最常用的数据库,我们也经常对mysql数据库进行各种优化,比如索引、隔离级别,从而让不同的数据库参数满足不同的项目需求。了解mysql事务的同学都知道,不同的事务隔离级别会带来各种不同的问题,最严格的隔离级别就是串行化,但是这种隔离级别我们平常却是非常少用的,因为这对数据库的性能有非常大的影响。那么我们在采用默认事务隔离基本的时候,mysql是怎么解决并发问题的呢?这就是我们需要详细了解的myql的一种多版本并发控制机制。


基本概念


网上有很多的官方定义,其实在我们看来 就是一种数据的多版本控制机制,也就说一行数据在mysql 内部维护了多个不同的版本,用来解决 读-写并发带来的各种问题,根据不同的隔离级别,不同的事务读取到同一行数据的不同版本


当前读和快照读


在这里我们在定义两个概念,这两个名词分别对应InnoDB引擎中两种读操作


       当前读:像 select lock in share mode ,select for update,这些操作都是当前读,也就是读取当前数据的最新记录。


       快照读:和当前读对比,不加锁的读就是快照读,也就说我们读取可能是某行数据的一个快照版本,并不是最新数据,这是MVCC实现的核心,这种快照读可以在不加锁的情况下解决并发操作带来的各种影响。


组成元素


       在innoDB中实现MVCC依靠三部分:三个隐藏字段、undo日志、Read View,每个组成部分的含义和作用我们会在下文继续介绍。


为什么要引入MVCC机制?


mysql并发场景:


       读-读:无所谓


       读-写:存在并发安全问题,可能造成事务隔离性问题,可能出现脏读、幻读、不可重复读


       写-写:存在并发完全问题,可能出现两类数据更新丢失


第一类:事务A撤销时,把已经提交了的事务B的更新数据覆盖了


第二类:事务A覆盖了事务B已经提交的数据。


而MVCC机制就是在不加锁的情况下,解决读-写带来的问题,也就是为事务分配一个单向增长的时间戳,为每个修改保存一个版本,版本与事务时间戳有关系,读操作只读该事务开始前的数据库快照。所以MVCC机制,可以做到下面这样


       在并发读写数据的时候,读写数据互相不阻塞,并且不会出现脏读、幻读、不可重复读等问题。但是不能解决更新丢失问题。


MVCC实现原理


隐藏字段


其实在我们平常定义的数据表中,除了 我们自己定义的业务字段意外,mysql 还内置了三个隐藏字段:


       DB_TRX_ID


       6byte,最近修改事务ID:记录创建这条记录/最后一次修改该记录的事务ID


       DB_ROLL_PTR


       7byte,回滚指针,指向这条记录的上一个版本


       DB_ROW_ID


       6byte,隐藏自增ID,如果表中没有指定主键,则innoDB引擎会默认使用该字段来创建聚集索引。


dac92beedfed4601bfe7238cf4bc1b76.png

undo日志


insert undo log


代表事务在insert新记录时产生的undo log,只在事务回滚时需要,并且在事务提交后会被立即删除


update undo log


事务在进行update或delete时产生的undo log 日志,不仅在事务回滚时需要,在快照读时也需要,所以不能随便删除,只有再快速读或者事务回滚不涉及到该日志时,对应的日志才会被purge线程统一删除。因为删除也是更新一个dele_flag标志位,所以这里的更新和删除是一种操作。


update undo log 产生流程:


一、 比如一个有个事务插入persion表插入了一条新记录,记录如下,name为Jerry, age为24岁,隐式主键是1,事务ID和回滚指针,我们假设为NULL

20190313213836406.png

二、 现在来了一个事务1对该记录的name做出了修改,改为Tom


在事务1修改该行(记录)数据时,数据库会先对该行加排他锁

然后把该行数据拷贝到undo log中,作为旧记录,既在undo log中有当前行的拷贝副本

拷贝完毕后,修改该行name为Tom,并且修改隐藏字段的事务ID为当前事务1的ID, 我们默认从1开始,之后递增,回滚指针指向拷贝到undo log的副本记录,既表示我的上一个版本就是它

事务提交后,释放锁



20190313220441831.png

三、 又来了个事务2修改person表的同一个记录,将age修改为30岁


在事务2修改该行数据时,数据库也先为该行加锁

然后把该行数据拷贝到undo log中,作为旧记录,发现该行记录已经有undo log了,那么最新的旧数据作为链表的表头,插在该行记录的undo log最前面

修改该行age为30岁,并且修改隐藏字段的事务ID为当前事务2的ID, 那就是2,回滚指针指向刚刚拷贝到undo log的副本记录

事务提交,释放锁


20190313220528630.png

从上面,我们就可以看出,不同事务或者相同事务的对同一记录的修改,会导致该记录的undo log成为一条记录版本线性表,既链表,undo log的链首就是最新的旧记录,链尾就是最早的旧记录(当然就像之前说的该undo log的节点可能是会purge线程清除掉,向图中的第一条insert undo log,其实在事务提交之后可能就被删除丢失了,不过这里为了演示,所以还放在这里)


Read View(读视图)


Read View 在我看来就是当时事务发生 快照读的时候 会对当前数据库生成一个快照,记录并维护当前所有的活跃的事务ID,数据库的事务ID是单边增加的,所以事务ID越大代表事务最新。


而之所以要维护一个这样的视图,主要是用来进行可见性判断的,也就说我们要根据这个视图来判断当前发生的快照读,是读取最新的数据还是读取undo log中某一个快照版本数据。


可见性算法


该算法的核心就是,将要被修改的数据的最新记录中的DB_TRX_ID(当前事务ID)取出来,与当前系统其它事务ID对比(这些事务ID就是在发生快照读的时候生成的Read View维护的),如果DB_TRX_ID根据Read View的属性进行比较以后,不符合可见性,那就通过DB_ROLL_PTR回滚指针去取出undo log日志中的DB_TRX_ID再次进行比较,一次遍历整个undo log日志版本链,直到找到满足条件的DB_TRX_ID,那么这个事务id就是当前事务能看到的快照数据。


比较过程


先定义三个全局变量


trx_list


事务id列表,其中存放的是当前系统中所有活跃的事务id


min_id


trx_list中最小值


max_id


Read View生成时刻系统中尚未分配的下一个事务ID,也就是目前当前系统最大事务ID+1


8b17f4628b004fafb176d2806ead9954.png


版本链比对规则:


1. 如果 DB_TRX_ID 落在绿色部分( DB_TRX_ID<min_id ),表示这个版本是已提交的事务生成的,这个数据是可见的;


2. 如果DB_TRX_ID 落在红色部分( DB_TRX_ID>max_id ),表示这个版本是由将来启动的事务生成的,是不可见的(若 DB_TRX_ID 就是当前自己的事务是可见的);


3. 如果DB_TRX_ID 落在黄色部分(min_id <=DB_TRX_ID<= max_id),那就包括两种情况


   a. 若 DB_TRX_ID 在视图数组中,表示这个版本是由还没提交的事务生成的,不可见(若DB_TRX_ID 就是当前自己的事务是可见的);


   b. 若 DB_TRX_ID不在视图数组中,表示这个版本是已经提交了的事务生成的,可见。


举一个例子


整体的流程是怎么样的呢?我们可以模拟一下


       1)当事务2对某行数据执行了快照读,数据库为该行数据生成一个Read View读视图,假设当前事务ID为2,此时还有事务1和事务3在活跃中,事务4在事务2快照读前一刻提交更新了,所以Read View记录了系统当前活跃事务1,3的ID,维护在一个列表上,假设我们称为trx_list


事务1 事务2 事务3 事务4
事务开始 事务开始 事务开始 事务开始
修改且已提交
进行中 快照读 进行中


2)Read View不仅仅会通过一个列表trx_list来维护事务2执行快照读那刻系统正活跃的事务ID,还会有两个属性up_limit_id(记录trx_list列表中事务ID最小的ID),low_limit_id(记录trx_list列表中事务ID最大的ID,也有人说快照读那刻系统尚未分配的下一个事务ID也就是目前已出现过的事务ID的最大值+1,我更倾向于后者 >>>资料传送门 | 呵呵一笑百媚生的回答) ;所以在这里例子中up_limit_id就是1,low_limit_id就是4 + 1 = 5,trx_list集合的值是1,3,Read View如下图


20190313224045780.png

3)我们的例子中,只有事务4修改过该行记录,并在事务2执行快照读前,就提交了事务,所以当前该行当前数据的undo log如下图所示;我们的事务2在快照读该行记录的时候,就会拿该行记录的DB_TRX_ID去跟up_limit_id,low_limit_id和活跃事务ID列表(trx_list)进行比较,判断当前事务2能看到该记录的版本是哪个。


2019031322511052.png


4)所以先拿该记录DB_TRX_ID字段记录的事务ID 4去跟Read View的的up_limit_id比较,看4是否小于up_limit_id(1),所以不符合条件,继续判断 4 是否大于等于 low_limit_id(5),也不符合条件,最后判断4是否处于trx_list中的活跃事务, 最后发现事务ID为4的事务不在当前活跃事务列表中, 符合可见性条件,所以事务4修改后提交的最新结果对事务2快照读时是可见的,所以事务2能读到的最新数据记录是事务4所提交的版本,而事务4提交的版本也是全局角度上最新的版本


不同事务隔离级别下的Read View


先来一个列子,在可重复读  Repeatable read  RR 隔离级别下


表1:


事务A 事务B
开启事务 开启事务
快照读(无影响)查询金额为500 快照读查询金额为500
更新金额为400
提交事务
select 快照读金额为500
select lock in share mode当前读金额为400


在上表的顺序下,事务B的在事务A提交修改后的快照读是旧版本数据,而当前读是实时新数据400

表2:


事务A 事务B
开启事务 开启事务
快照读(无影响)查询金额为500
更新金额为400
提交事务
select 快照读金额为400
select lock in share mode当前读金额为400


而在表2这里的顺序中,事务B在事务A提交后的快照读和当前读都是实时的新数据400,这是为什么呢?


这里与上表的唯一区别仅仅是表1的事务B在事务A修改金额前快照读过一次金额数据,而表2的事务B在事务A修改金额前没有进行过快照读。


所以我们知道事务中快照读的结果是非常依赖该事务首次出现快照读的地方,即某个事务中首次出现快照读的地方非常关键,它有决定该事务后续快照读结果的能力


读已提交 RC,可重复读 级别下的InnoDB快照读有什么不同?


正是Read View生成时机的不同,从而造成RC,RR级别下快照读的结果的不同


在RR级别下的某个事务的对某条记录的第一次快照读会创建一个快照及Read View, 将当前系统活跃的其他事务记录起来,此后在调用快照读的时候,还是使用的是同一个Read View,所以只要当前事务在其他事务提交更新之前使用过快照读,那么之后的快照读使用的都是同一个Read View,所以对之后的修改不可见;


即RR级别下,快照读生成Read View时,Read View会记录此时所有其他活动事务的快照,这些事务的修改对于当前事务都是不可见的。而早于Read View创建的事务所做的修改均是可见


而在RC级别下的,事务中,每次快照读都会新生成一个快照和Read View, 这就是我们在RC级别下的事务中可以看到别的事务提交的更新的原因


总之在RC隔离级别下,是每个快照读都会生成并获取最新的Read View;而在RR隔离级别下,则是同一个事务中的第一个快照读才会创建Read View, 之后的快照读获取的都是同一个Read View。

相关实践学习
每个IT人都想学的“Web应用上云经典架构”实战
本实验从Web应用上云这个最基本的、最普遍的需求出发,帮助IT从业者们通过“阿里云Web应用上云解决方案”,了解一个企业级Web应用上云的常见架构,了解如何构建一个高可用、可扩展的企业级应用架构。
MySQL数据库入门学习
本课程通过最流行的开源数据库MySQL带你了解数据库的世界。 &nbsp; 相关的阿里云产品:云数据库RDS MySQL 版 阿里云关系型数据库RDS(Relational Database Service)是一种稳定可靠、可弹性伸缩的在线数据库服务,提供容灾、备份、恢复、迁移等方面的全套解决方案,彻底解决数据库运维的烦恼。 了解产品详情:&nbsp;https://www.aliyun.com/product/rds/mysql&nbsp;
目录
相关文章
|
2月前
|
存储 SQL 关系型数据库
mysql底层原理:索引、慢查询、 sql优化、事务、隔离级别、MVCC、redolog、undolog(图解+秒懂+史上最全)
mysql底层原理:索引、慢查询、 sql优化、事务、隔离级别、MVCC、redolog、undolog(图解+秒懂+史上最全)
mysql底层原理:索引、慢查询、 sql优化、事务、隔离级别、MVCC、redolog、undolog(图解+秒懂+史上最全)
|
9月前
|
SQL 安全 关系型数据库
【MySQL基础篇】事务(事务操作、事务四大特性、并发事务问题、事务隔离级别)
事务是MySQL中一组不可分割的操作集合,确保所有操作要么全部成功,要么全部失败。本文利用SQL演示并总结了事务操作、事务四大特性、并发事务问题、事务隔离级别。
4130 56
【MySQL基础篇】事务(事务操作、事务四大特性、并发事务问题、事务隔离级别)
|
7月前
|
SQL 关系型数据库 MySQL
京东面试:MySQL MVCC是如何实现的?如何通过MVCC实现读已提交、可重复读隔离级别的?
1.请解释什么是MVCC,它在数据库中的作用是什么? 2.在MySQL中,MVCC是如何实现的?请简述其工作原理。 3.MVCC是如何解决读-写和写-写冲突的? 4.在并发环境中,当多个事务同时读取同一行数据时,MVCC是如何保证每个事务看到的数据版本是一致的? 5.MVCC如何帮助提高数据库的并发性能?
京东面试:MySQL MVCC是如何实现的?如何通过MVCC实现读已提交、可重复读隔离级别的?
|
8月前
|
SQL 关系型数据库 MySQL
vb6读取mysql,用odbc mysql 5.3版本驱动
通过以上步骤,您可以在VB6中使用ODBC MySQL 5.3驱动连接MySQL数据库并读取数据。配置ODBC数据源、编写VB6代码
195 32
|
9月前
|
关系型数据库 MySQL Linux
MySQL版本升级(8.0.31->8.0.37)
本次升级将MySQL从8.0.31升级到8.0.37,采用就地升级方式。具体步骤包括:停止MySQL服务、备份数据目录、下载并解压新版本的RPM包,使用`yum update`命令更新已安装的MySQL组件,最后启动MySQL服务并验证版本。整个过程需确保所有相关RPM包一同升级,避免部分包遗漏导致的问题。官方文档提供了详细指导,确保升级顺利进行。
944 16
|
8月前
|
SQL 存储 关系型数据库
MySQL进阶突击系列(05)突击MVCC核心原理 | 左右护法ReadView视图和undoLog版本链强强联合
2024年小结:感谢阿里云开发者社区每月的分享交流活动,支持持续学习和进步。过去五个月投稿29篇,其中17篇获高分认可。本文详细介绍了MySQL InnoDB存储引擎的MVCC机制,包括数据版本链、readView视图及解决脏读、不可重复读、幻读问题的demo演示。
|
9月前
|
SQL 存储 关系型数据库
Mysql并发控制和日志
通过深入理解和应用 MySQL 的并发控制和日志管理技术,您可以显著提升数据库系统的效率和稳定性。
347 10
|
10月前
|
关系型数据库 MySQL
mysql 5.7.x版本查看某张表、库的大小 思路方案说明
mysql 5.7.x版本查看某张表、库的大小 思路方案说明
228 5
|
10月前
|
关系型数据库 MySQL
mysql 5.7.x版本查看某张表、库的大小 思路方案说明
mysql 5.7.x版本查看某张表、库的大小 思路方案说明
175 1
|
3月前
|
人工智能 运维 关系型数据库
数据库运维:mysql 数据库迁移方法-mysqldump
本文介绍了MySQL数据库迁移的方法与技巧,重点探讨了数据量大小对迁移方式的影响。对于10GB以下的小型数据库,推荐使用mysqldump进行逻辑导出和source导入;10GB以上可考虑mydumper与myloader工具;100GB以上则建议物理迁移。文中还提供了统计数据库及表空间大小的SQL语句,并讲解了如何使用mysqldump导出存储过程、函数和数据结构。通过结合实际应用场景选择合适的工具与方法,可实现高效的数据迁移。
639 1

推荐镜像

更多