目录
1.什么是MVCC
2快照读与当前读
2.1 快照读
2.2当前读
3.复习
3.1 再谈隔离级别
3.2 隐藏字段、Undo Log版本链
4、MVCC实现原理之ReadView
4.1什么是ReadView
4.2 设计思路
4.3 ReadView的规则
5.举例说明
5.1 READ COMMITTED
5.2 REPEATABLE READ
5.3 如何解决幻读
首页 数据库 mysql教程 MySQL多版本并发控制MVCC实例分析

MySQL多版本并发控制MVCC实例分析

Jun 03, 2023 am 11:51 AM
mysql mvcc

    1.什么是MVCC

    MVCC (Multiversion Concurrency Control),多版本并发控制。顾名思义,MVCC是通过数据行的多个版本管理来实现数据库的并发控制。这项技术使得在InnoDB的事务隔离级别下执行一致性读.操作有了保证。换言之,就是为了查询一些正在被另一个事务更新的行,并且可以看到它们被更新之前的值,这样在做查询的时候就不用等待另一个事务释放锁。

    MVCC没有正式的标准,在不同的DBMS中MVCC的实现方式可能是不同的,也不是普遍使用的(大家可以参考相关的DBMS文档)。这里讲解InnoDB中 MVCC的实现机制(MySQL其它的存储引擎并不支持它)

    2快照读与当前读

    MVCC在MySQL InnoDB中的实现主要是为了提高数据库并发性能,用更好的方式去处理读-写冲突,做到即使有读写冲突时,也能做到不加锁非阻塞并发读,而这个读指的就是快照读,而非当前读。当前读实际上是一种加锁的操作,是悲观锁的实现。而MVCC本质是采用乐观锁思想的一种方式。

    2.1 快照读

    快照读又叫一致性读,读取的是快照数据。不加锁的简单的SELECT都属于快照读即不加锁的非阻塞读;比如这样:

    select * from player where ...
    登录后复制

    之所以出现快照读的情况,是基于提高并发性能的考虑,快照读的实现是基于MVCC,它在很多情况下,避免了加锁操作,降低了开销。

    既然是基于多版本,那么快照读可能读到的并不一定是数据的最新版本,而有可能是之前的历史版本。

    快照读的前提是隔离级别不是串行级别,串行级别下的快照读会退化成当前读。

    2.2当前读

    当前读读取的是记录的最新版本(最新数据,而不是历史版本的数据),读取时还要保证其他并发事务不能修改当前记录,会对读取的记录进行加锁。加锁的SELECT,或者对数据进行增删改都会进行当前读。比如:

    MySQL多版本并发控制MVCC实例分析

    3.复习

    3.1 再谈隔离级别

    我们知道事务有4个隔离级别,可能存在三种并发问题:

    MySQL多版本并发控制MVCC实例分析

    在MysQL 中,默认的隔离级别是可重复读,可以解决脏读和不可重复读的问题,如果仅从定义的角度来看,它并不能解决幻读问题。如果我们想要解决幻读问题,就需要采用串行化的方式,也就是将隔离级别提升到最高,但这样一来就会大幅降低数据库的事务并发能力。

    MVCC可以不采用锁机制,而是通过乐观锁的方式来解决不可重复读和幻读问题!它可以在大多数情况下替代行级锁,降低系统的开销。

    MySQL多版本并发控制MVCC实例分析

    3.2 隐藏字段、Undo Log版本链

    回顾一下undo日志的版本链,对于使用InnoDB存储引擎的表来说,它的聚簇索引记录中都包含两个必要的隐藏列。

    trx_id:每次一个事务对某条聚簇索引记录进行改动时,都会把该事务的事务id赋值给trx_id隐藏列。roll_pointer:每次对某条聚簇索引记录进行改动时,都会把旧的版本写入到undo日志中,然后这个隐藏列就相当于一个指针,可以通过它来找到该记录修改前的信息。

    MySQL多版本并发控制MVCC实例分析

    MySQL多版本并发控制MVCC实例分析

    MySQL多版本并发控制MVCC实例分析

    MySQL多版本并发控制MVCC实例分析

    4、MVCC实现原理之ReadView

    MVCC的实现依赖于:隐藏字段、Undo Log、Read View

    4.1什么是ReadView

    在MVCC机制中,多个事务对同一个行记录进行更新会产生多个历史快照,这些历史快照保存在Undo Log里。如果一个事务想要查询这个行记录,需要读取哪个版本的行记录呢?这时就需要用到ReadView了,它帮我们解决了行的可见性问题。

    当事务使用MVCC机制进行快照读操作时,会产生一个读视图,这个视图就是ReadView。当事务启动时,会生成数据库系统当前的一个快照,InnoDB为每个事务构造了一个数组,用来记录并维护系统当前活跃事务的lD(“"活跃"指的就是,启动了但还没提交)。

    4.2 设计思路

    使用READ UNCOMMITTED隔离级别的事务,由于可以读到未提交事务修改过的记录,所以直接读取记录的最新版本就好了。

    使用SERIALIZABLE隔离级别的事务,InnoDB规定使用加锁的方式来访问记录。

    使用READ COMMITTEDREPEATABLE READ隔离级别的事务,都必须保证读到已经提交了的事务修改过的记录。假如另一个事务已经修改了记录但是尚未提交,是不能直接读取最新版本的记录的,核心问题就是需要判断一下版本链中的哪个版本是当前事务可见的,这是ReadView要解决的主要问题。

    这个ReadView中主要包含4个比较重要的内容,分别如下:

    MySQL多版本并发控制MVCC实例分析

    4.3 ReadView的规则

    有了这个ReadView,这样在访问某条记录时,只需要按照下边的步骤判断记录的某个版本是否可见。

    • 如果被访问版本的trx_id属性值与ReadView中的creator_trx_id值相同,意味着当前事务在访问它自己修改过的记录,所以该版本可以被当前事务访问。

    • 如果被访问版本的trx_id属性值小于ReadView中的up_limit_id值,表明生成该版本的事务在当前事务生成ReadView前已经提交,所以该版本可以被当前事务访问。

    • 如果被访问版本的trx_id属性值大于或等于ReadView中的low_limit_id值,表明生成该版本的事务在当前事务生成ReadView后才开启,所以该版本不可以被当前事务访问。

    • 如果被访问版本的trx_id属性值在ReadView的up_limit_idlow_limit_id之间,那就需要判断一下trx_id属性值是不是在trx_ids列表中。如果在,说明创建ReadView时生成该版本的事务还是活跃的,该版本不可以被访问。如果不在,说明创建ReadView时生成该版本的事务已经被提交,该版本可以被访问。 4.4 MVCC整体操作流程

    了解了这些概念之后,我们来看下当查询一条记录的时候,系统如何通过MVCC找到它:

    • 1.首先获取事务自己的版本号,也就是事务ID;

    • 2.获取ReadView;

    • 3.查询得到的数据,然后与ReadView中的事务版本号进行比较;

    • 4.如果不符合Readview规则,就需要从Undo Log中获取历史快照;

    • 5.最后返回符合规则的数据。

    如果某个版本的数据对当前事务不可见的话,那就顺着版本链找到下一个版本的数据,继续按照上边的步骤判断可见性,依此类推,直到版本链中的最后一个版本。如果最近版本不可见,则该记录对该事务不可见,并且查询结果中不包含该记录。

    InnoDB中,MVCC是通过Undo Log + Read View进行数据读取,Undo Log保存了历史快照,而Read View规则帮我们判断当前版本的数据是否可见。

    在隔离级别为读已提交(Read Committed)时,一个事务中的每一次select查询都会重新获取一次Read View。

    MySQL多版本并发控制MVCC实例分析

    当隔离级别为可重读的时候,就避免了不可重复读,这是因为一个事务只在第一次select的时候会获取一次Read View,而后面所有的select都会复用这个Read View,

    如下所示:

    MySQL多版本并发控制MVCC实例分析

    5.举例说明

    假设现在student表中只有一条由事务id8事务插入一条记录:

    MySQL多版本并发控制MVCC实例分析

    MVCC只能在READ COMMITTED和REPEATABLE READ两个隔离级别下工作。接下来看一下READ COMMITTEDREPEATABLE READ所谓的生成Readview的时机不同到底不同在哪里。

    5.1 READ COMMITTED

    隔离级别下:

    READ COMMITTED:每次读取数据前都生成一个ReadView

    现在有两个事务id分别为10、20的事务在执行:

    MySQL多版本并发控制MVCC实例分析

    说明:事务执行过程中,只有在第一次真正修改记录时(比如使用INSERT、DELETE、UPDATE语句),才会被分配一个单独的事务id,这个事务id是递增的。所以我们才在事务2中更新一些别的表的记录,目的是让它分配事务id。

    此刻,表student中id为1的记录得到的版本链表如下所示:

    MySQL多版本并发控制MVCC实例分析

    假设现在有一个使用READ COMMITED隔离级别的事务开始执行:

    MySQL多版本并发控制MVCC实例分析

    这个SELECT1的执行过程如下:

    步骤1∶在执行SELECT语句时会先生成一个ReadView ,ReadView的trx_ids列表的内容就是[10,20],up_limit_id10, low_limit_id21 , creator_trx_id0
    步骤2:从版本链中挑选可见的记录,从图中看出,最新版本的列name的内容是'王五',该版本的trx_id值为10,在trx_ids列表内,所以不符合可见性要求,根据roll_pointer跳到下一个版本。
    步骤3:下一个版本的列name的内容是'李四',该版本的trx_id值也为10,也在trx_ids列表内,所以也不符合要求,继续跳到下一个版本。
    步骤4:下一个版本的列 name的内容是‘张三',该版本的trx_id值为8,小于ReadView 中的up_limit_id值10,所以这个版本是符合要求的,最后返回给用户的版本就是这条列name为‘张三’的记录。

    之后,我们把事务id10的事务提交一下:

    MySQL多版本并发控制MVCC实例分析

    然后再到事务id20的事务中更新一下表studentid1的记录:

    MySQL多版本并发控制MVCC实例分析

    此刻,表student中id为1的记录的版本链就长这样:

    MySQL多版本并发控制MVCC实例分析

    然后再到刚才使用READ COMMITTED隔离级别的事务中继续查找id为1的记录,如下:

    MySQL多版本并发控制MVCC实例分析

    这个SELECT2的执行过程如下:

    步骤1∶在执行SELECT语句时会又会单独生成一个ReadView,该ReadView的trx_ids列表的内容就是[20],up_limit_id为20,low_limit_id为21, creator_trx_id为0。
    步骤2:从版本链中挑选可见的记录,从图中看出,最新版本的列name的内容是‘宋八’,该版本的tr×_id值为20,在trx_ids列表内,所以不符合可见性要求,根据roll.pointer跳到下一个版本。
    步骤3∶下一个版本的列name的内容是’钱七’,该版本的trx_id值为20,也在trx_ids列表内,所以也不符合要求,继续跳到下一个版本。
    步骤4∶下一个版本的列name的内容是’王五’,该版本的trx_id值为10,小于ReadView中的up_limit.id值20,所以这个版本是符合要求的,最后返回给用户的版本就是这条列name为’王五’的记录。
    以此类推,如果之后事务id为20的记录也提交了,再次在使用READ CONMMITTED隔离级别的事务中查询表student中id值为1的记录时,得到的结果就是‘宋八’了,具体流程我们就不分析了。

    5.2 REPEATABLE READ

    隔离级别下:

    使用REPEATABLE READ隔离级别的事务来说,只会在第一次执行查询语句时生成一个ReadView,之后的查询就不会重复生成了。

    比如,系统里有两个事务id分别为10、20的事务在执行:

    MySQL多版本并发控制MVCC实例分析

    此刻,表student中id为1的记录得到的版本链表如下所示:

    MySQL多版本并发控制MVCC实例分析

    假设现在有一个使用REPEATABLE READ隔离级别的事务开始执行:

    MySQL多版本并发控制MVCC实例分析

    此时执行过程与read committed相同

    MySQL多版本并发控制MVCC实例分析

    MySQL多版本并发控制MVCC实例分析

    然后再到刚才使用REPEATABLE READ隔离级别的事务中继续查找id为1的记录,如下:

    MySQL多版本并发控制MVCC实例分析

    这个SELECT2的执行过程如下:

    步骤1:因为当前事务的隔离级别为REPEATABLE READ,而之前在执行SELECT1时已经生成过ReadView了,所以此时直接复用之前的ReadView,之前的ReadView的trx_ids列表的内容就是[10,20],up_limit_id为10, low_limit_id为21 , creator_trx_id为0。
    步骤2:然后从版本链中挑选可见的记录,从图中可以看出,最新版本的列name的内容是’宋八’trx_id值为20,在trx_ids列表内,所以不符合可见性要求,根据roll_pointer跳到下一个版本。
    步骤3:下一个版本的列name的内容是’钱七’,该版本的trx_id值为20,也在trx_ids列表内合要求,继续跳到下一个版本。
    步骤4:下一个版本的列name的内容是’王五’,该版本的trx_id值为10,而trx_ids列表中是包含值为10的事务id的,所以该版本也不符合要求,同理下一个列name的内容是’李四’的版本也不符合要求。继续跳到下个版本。
    步聚5∶下一个版本的列name的内容是’张三’,该版本的trx_id值为80,小于Readview中的up_limit_id值10,所以这个版本是符合要求的,最后返回给用户的版本就是这条列c为‘张三’的记录。
    两次SELECT查询得到的结果是重复的,记录的列c值都是’张三’,这就是可重复读的含义。如果我们之后再把事务id为20的记录提交了,然后再到刚才使用REPEATABLE READ隔离级刷的事务中继续查找这个id为1的记录,得到的结果还是’张三’,具体执行过程大家可以自己分析一下。

    5.3 如何解决幻读

    假设现在表student中只有一条数据,数据内容中,主键id=1,隐藏的trx_id=10,它的undo log如下图所示。

    MySQL多版本并发控制MVCC实例分析

    假设现在有事务A和事务B并发执行,事务A的事务id为20,事务B的事务id为30。
    步骤1:事务A开始第一次查询数据,查询的SQL语句如下。

    select * from student where id > 1;
    登录后复制

    在开始查询之前,MySQL会为事务A产生一个ReadView,此时ReadView的内容如下: trx_ids=[20, 30 ] ,up_limit_id=20 , low_limit_id=31 , creator_trx_id=20。

    因为表student只有一条符合条件 where id>=1 的数据,所以会被查询出来。然后根据ReadView机制,发现该行数据的trx_id=10,小于事务A的ReadView里up_limit_id,这表示这条数据是事务A开启之前,其他事务就已经提交了的数据,因此事务A可以读取到。

    结论:事务A的第一次查询,能读取到一条数据,id=1。

    步骤2∶接着事务B(trx_id=30),往表student中新插入两条数据,并提交事务。

    insert into student(id,name) values(2,'李四');
    insert into student(id,name) values(3,'王五');
    登录后复制

    此时表student中就有三条数据了,对应的undo如下图所示:

    MySQL多版本并发控制MVCC实例分析

    步骤3∶接着事务A开启第二次查询,根据可重复读隔离级别的规则,此时事务A并不会再重新生成ReadView。此时表student中的3条数据都满足 where id>=1的条件,因此会先查出来。然后根据ReadView机制,判断每条数据是不是都可以被事务A看到。
    1)首先 id=1的这条数据,前面已经说过了,可以被事务A看到。
    2)然后是id=2的数据,它的trx_id=30,此时事务A发现,这个值处于up_limit_id和low_limit_id之间,因此还需要再判断30是否处于trx_ids数组内。由于事务A的trx_ids=[20,30],因此在数组内,这表示id=2的这条数据是与事务A在同一时刻启动的其他事务提交的,所以这条数据不能让事务A看到。
    3)同理,id=3的这条数据, trx_id 也为30,因此也不能被事务A看见。

    MySQL多版本并发控制MVCC实例分析

    结论:最终事务A的第二次查询,只能查询出id=1的这条数据。这和事务A的第一次查询的结果是一样的,因此没有出现幻读现象,所以说在 MySQL的可重复续隔离级别下,不存在幻读问题。

    以上是MySQL多版本并发控制MVCC实例分析的详细内容。更多信息请关注PHP中文网其他相关文章!

    本站声明
    本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

    热AI工具

    Undresser.AI Undress

    Undresser.AI Undress

    人工智能驱动的应用程序,用于创建逼真的裸体照片

    AI Clothes Remover

    AI Clothes Remover

    用于从照片中去除衣服的在线人工智能工具。

    Undress AI Tool

    Undress AI Tool

    免费脱衣服图片

    Clothoff.io

    Clothoff.io

    AI脱衣机

    AI Hentai Generator

    AI Hentai Generator

    免费生成ai无尽的。

    热门文章

    R.E.P.O.能量晶体解释及其做什么(黄色晶体)
    3 周前 By 尊渡假赌尊渡假赌尊渡假赌
    R.E.P.O.最佳图形设置
    3 周前 By 尊渡假赌尊渡假赌尊渡假赌
    R.E.P.O.如果您听不到任何人,如何修复音频
    3 周前 By 尊渡假赌尊渡假赌尊渡假赌
    WWE 2K25:如何解锁Myrise中的所有内容
    4 周前 By 尊渡假赌尊渡假赌尊渡假赌

    热工具

    记事本++7.3.1

    记事本++7.3.1

    好用且免费的代码编辑器

    SublimeText3汉化版

    SublimeText3汉化版

    中文版,非常好用

    禅工作室 13.0.1

    禅工作室 13.0.1

    功能强大的PHP集成开发环境

    Dreamweaver CS6

    Dreamweaver CS6

    视觉化网页开发工具

    SublimeText3 Mac版

    SublimeText3 Mac版

    神级代码编辑软件(SublimeText3)

    MySQL:初学者的数据管理易用性 MySQL:初学者的数据管理易用性 Apr 09, 2025 am 12:07 AM

    MySQL适合初学者使用,因为它安装简单、功能强大且易于管理数据。1.安装和配置简单,适用于多种操作系统。2.支持基本操作如创建数据库和表、插入、查询、更新和删除数据。3.提供高级功能如JOIN操作和子查询。4.可以通过索引、查询优化和分表分区来提升性能。5.支持备份、恢复和安全措施,确保数据的安全和一致性。

    忘记数据库密码,能在Navicat中找回吗? 忘记数据库密码,能在Navicat中找回吗? Apr 08, 2025 pm 09:51 PM

    Navicat本身不存储数据库密码,只能找回加密后的密码。解决办法:1. 检查密码管理器;2. 检查Navicat的“记住密码”功能;3. 重置数据库密码;4. 联系数据库管理员。

    navicat premium怎么创建 navicat premium怎么创建 Apr 09, 2025 am 07:09 AM

    使用 Navicat Premium 创建数据库:连接到数据库服务器并输入连接参数。右键单击服务器并选择“创建数据库”。输入新数据库的名称和指定字符集和排序规则。连接到新数据库并在“对象浏览器”中创建表。右键单击表并选择“插入数据”来插入数据。

    mysql怎么复制表 mysql怎么复制表 Apr 08, 2025 pm 07:24 PM

    在 MySQL 中复制表需要创建新表、插入数据、设置外键、复制索引、触发器、存储过程和函数。具体步骤包括:创建具有相同结构的新表。将数据从原始表插入新表。设置相同的外键约束(如果原始表有)。创建相同索引。创建相同触发器(如果原始表有)。创建相同存储过程或函数(如果原始表使用了)。

    Navicat for MariaDB如何查看数据库密码? Navicat for MariaDB如何查看数据库密码? Apr 08, 2025 pm 09:18 PM

    Navicat for MariaDB 无法直接查看数据库密码,因为密码以加密形式存储。为确保数据库安全,有三个方法可重置密码:通过 Navicat 重置密码,设置复杂密码。查看配置文件(不推荐,风险高)。使用系统命令行工具(不推荐,需要对命令行工具精通)。

    mysql怎么查看 mysql怎么查看 Apr 08, 2025 pm 07:21 PM

    通过以下命令查看 MySQL 数据库:连接到服务器:mysql -u 用户名 -p 密码运行 SHOW DATABASES; 命令获取所有现有数据库选择数据库:USE 数据库名;查看表:SHOW TABLES;查看表结构:DESCRIBE 表名;查看数据:SELECT * FROM 表名;

    mysql怎么复制粘贴 mysql怎么复制粘贴 Apr 08, 2025 pm 07:18 PM

    MySQL 中的复制粘贴包含以下步骤:选择数据,使用 Ctrl C(Windows)或 Cmd C(Mac)复制;在目标位置右键单击,选择“粘贴”或使用 Ctrl V(Windows)或 Cmd V(Mac);复制的数据将插入到目标位置,或替换现有数据(取决于目标位置是否已存在数据)。

    navicat如何执行sql navicat如何执行sql Apr 08, 2025 pm 11:42 PM

    在 Navicat 中执行 SQL 的步骤:连接到数据库。创建 SQL 编辑器窗口。编写 SQL 查询或脚本。单击“运行”按钮执行查询或脚本。查看结果(如果执行查询的话)。

    See all articles