​​​​​​​

版本链

对于使用InnoDB存储引擎的表来说,它的聚簇索引记录中都包含两个必要的隐藏列(row_id并不是必要的,我们创建的表中有主键或者非NULL唯一键时都不会包含row_id列):

1)trx_id:每次对某条聚簇索引记录进行改动时,都会把对应的事务id赋值给trx_id隐藏列。

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

比方说我们的表t现在只包含一条记录:

mysql> SELECT * FROM t;

+----+--------+

| id | c      |

+----+--------

+|  1 | 刘备   |

+----+--------+

1 row in set (0.01 sec)

假设插入该记录的事务id为80,那么此刻该条记录的示意图如下所示:

假设之后两个id分别为100、200的事务对这条记录进行UPDATE操作,操作流程如下:

小贴士: 能不能在两个事务中交叉更新同一条记录呢?哈哈,这是不可以滴,第一个事务更新了某条记录后,就会给这条记录加锁,另一个事务再次更新时就需要等待第一个事务提交了,把锁释放之后才可以继续更新。本篇文章不是讨论锁的,有关锁的更多细节我们之后再说。

每次对记录进行改动,都会记录一条undo日志,每条undo日志也都有一个roll_pointer属性(INSERT操作对应的undo日志没有该属性,因为该记录并没有更早的版本),可以将这些undo日志都连起来,串成一个链表,所以现在的情况就像下图一样:

对该记录每次更新后,都会将旧值放到一条undo日志中,就算是该记录的一个旧版本,随着更新次数的增多,所有的版本都会被roll_pointer属性连接成一个链表,我们把这个链表称之为版本链,版本链的头节点就是当前记录最新的值。另外,每个版本中还包含生成该版本时对应的事务id,这个信息很重要,我们稍后就会用到。

ReadView

对于使用READ UNCOMMITTED隔离级别的事务来说,直接读取记录的最新版本就好了,对于使用SERIALIZABLE隔离级别的事务来说,使用加锁的方式来访问记录。对于使用READ COMMITTED和REPEATABLE READ隔离级别的事务来说,就需要用到我们上边所说的版本链了,核心问题就是:需要判断一下版本链中的哪个版本是当前事务可见的。所以设计InnoDB的大叔提出了一个ReadView的概念,这个ReadView中主要包含当前系统中还有哪些活跃的读写事务,把它们的事务id放到一个列表中,我们把这个列表命名为为m_ids。这样在访问某条记录时,只需要按照下边的步骤判断记录的某个版本是否可见:

1)如果被访问版本的trx_id属性值小于m_ids列表中最小的事务id,表明生成该版本的事务在生成ReadView前已经提交,所以该版本可以被当前事务访问

2)如果被访问版本的trx_id属性值大于m_ids列表中最大的事务id,表明生成该版本的事务在生成ReadView后才生成,所以该版本不可以被当前事务访问。

3)如果被访问版本的trx_id属性值在m_ids列表中最大的事务id和最小事务id之间,那就需要判断一下trx_id属性值是不是在m_ids列表中,如果在,说明创建ReadView时生成该版本的事务还是活跃的,该版本不可以被访问;如果不在,说明创建ReadView时生成该版本的事务已经被提交,该版本可以被访问。
4)如果某个版本的数据对当前事务不可见的话,那就顺着版本链找到下一个版本的数据,继续按照上边的步骤判断可见性,依此类推,直到版本链中的最后一个版本,如果最后一个版本也不可见的话,那么就意味着该条记录对该事务不可见,查询结果就不包含该记录。

在MySQL中,READ COMMITTED和REPEATABLE READ隔离级别的的一个非常大的区别就是它们生成ReadView的时机不同,我们来看一下。

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

比方说现在系统里有两个id分别为100、200的事务在执行:

小贴士: 事务执行过程中,只有在第一次真正修改记录时(比如使用INSERT、DELETE、UPDATE语句),才会被分配一个单独的事务id,这个事务id是递增的。

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

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

这个SELECT1的执行过程如下:

1)在执行SELECT语句时会先生成一个ReadView,ReadView的m_ids列表的内容就是[100, 200]。

2)然后从版本链中挑选可见的记录,从图中可以看出,最新版本的列c的内容是'张飞',该版本的trx_id值为100,在m_ids列表内,所以不符合可见性要求,根据roll_pointer跳到下一个版本。

3)下一个版本的列c的内容是'关羽',该版本的trx_id值也为100,也在m_ids列表内,所以也不符合要求,继续跳到下一个版本。

4)下一个版本的列c的内容是'刘备',该版本的trx_id值为80,小于m_ids列表中最小的事务id100,所以这个版本是符合要求的,最后返回给用户的版本就是这条列c为'刘备'的记录。

之后,我们把事务id为100的事务提交一下,就像这样:

然后再到事务id为200的事务中更新一下表t中id为1的记录:

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

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

这个SELECT2的执行过程如下:

1)在执行SELECT语句时会先生成一个ReadView,ReadView的m_ids列表的内容就是[200](事务id为100的那个事务已经提交了,所以生成快照时就没有它了)。

2)然后从版本链中挑选可见的记录,从图中可以看出,最新版本的列c的内容是'诸葛亮',该版本的trx_id值为200,在m_ids列表内,所以不符合可见性要求,根据roll_pointer跳到下一个版本。

3)下一个版本的列c的内容是'赵云',该版本的trx_id值为200,也在m_ids列表内,所以也不符合要求,继续跳到下一个版本。

4)下一个版本的列c的内容是'张飞',该版本的trx_id值为100,比m_ids列表中最小的事务id200还要小,所以这个版本是符合要求的,最后返回给用户的版本就是这条列c为'张飞'的记录。

以此类推,如果之后事务id为200的记录也提交了,再此在使用READ COMMITTED隔离级别的事务中查询表t中id值为1的记录时,得到的结果就是'诸葛亮'了,具体流程我们就不分析了。总结一下就是:使用READ COMMITTED隔离级别的事务在每次查询开始时都会生成一个独立的ReadView。

REPEATABLE READ ---在第一次读取数据时生成一个ReadView

对于使用REPEATABLE READ隔离级别的事务来说,只会在第一次执行查询语句时生成一个ReadView,之后的查询就不会重复生成了。我们还是用例子看一下是什么效果。

比方说现在系统里有两个id分别为100、200的事务在执行:

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

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

这个SELECT1的执行过程如下:

1)在执行SELECT语句时会先生成一个ReadView,ReadView的m_ids列表的内容就是[100, 200]。

2)然后从版本链中挑选可见的记录,从图中可以看出,最新版本的列c的内容是'张飞',该版本的trx_id值为100,在m_ids列表内,所以不符合可见性要求,根据roll_pointer跳到下一个版本。

3)下一个版本的列c的内容是'关羽',该版本的trx_id值也为100,也在m_ids列表内,所以也不符合要求,继续跳到下一个版本。

4)下一个版本的列c的内容是'刘备',该版本的trx_id值为80,小于m_ids列表中最小的事务id100,所以这个版本是符合要求的,最后返回给用户的版本就是这条列c为'刘备'的记录。

之后,我们把事务id为100的事务提交一下,就像这样:

然后再到事务id为200的事务中更新一下表t中id为1的记录:

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

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

这个SELECT2的执行过程如下:

1)因为之前已经生成过ReadView了,所以此时直接复用之前的ReadView,之前的ReadView中的m_ids列表就是[100, 200]。

2)然后从版本链中挑选可见的记录,从图中可以看出,最新版本的列c的内容是'诸葛亮',该版本的trx_id值为200,在m_ids列表内,所以不符合可见性要求,根据roll_pointer跳到下一个版本。

3)下一个版本的列c的内容是'赵云',该版本的trx_id值为200,也在m_ids列表内,所以也不符合要求,继续跳到下一个版本。

4)下一个版本的列c的内容是'张飞',该版本的trx_id值为100,而m_ids列表中是包含值为100的事务id的,所以该版本也不符合要求,同理下一个列c的内容是'关羽'的版本也不符合要求。继续跳到下一个版本。

5)下一个版本的列c的内容是'刘备',该版本的trx_id值为80,80小于m_ids列表中最小的事务id100,所以这个版本是符合要求的,最后返回给用户的版本就是这条列c为'刘备'的记录。

也就是说两次SELECT查询得到的结果是重复的,记录的列c值都是'刘备',这就是可重复读的含义。如果我们之后再把事务id为200的记录提交了,之后再到刚才使用REPEATABLE READ隔离级别的事务中继续查找这个id为1的记录,得到的结果还是'刘备',具体执行过程大家可以自己分析一下。

MVCC总结

从上边的描述中我们可以看出来,所谓的MVCC(Multi-Version Concurrency Control ,多版本并发控制)指的就是在使用READ COMMITTD、REPEATABLE READ这两种隔离级别的事务在执行普通的SEELCT操作时访问记录的版本链的过程,这样子可以使不同事务的读-写、写-读操作并发执行,从而提升系统性能。READ COMMITTD、REPEATABLE READ这两个隔离级别的一个很大不同就是生成ReadView的时机不同,READ COMMITTD在每一次进行普通SELECT操作前都会生成一个ReadView,而REPEATABLE READ只在第一次进行普通SELECT操作前生成一个ReadView,之后的查询操作都重复这个ReadView就好了。

mysql实现可重复读(解决幻读)的原理(MVCC机制的版本链和读视图)相关推荐

  1. mysql串行化防幻读原理_透彻解读mysql的可重复读、幻读及实现原理

    目录 一.事务的隔离级别 二.mysql怎么实现的可重复读 举例说明MVCC的实现 MVCC逻辑流程-插入 MVCC逻辑流程-删除 MVCC逻辑流程-修改 MVCC逻辑流程-查询 三.幻读 快照读和当 ...

  2. mysql可重复读和幻读的理解

    mysql可重复读和幻读的理解 可重复读和幻读的定义 最后总结 参考资料 很多教程和书籍对mysql的可重复读和幻读的解释都比较含糊,本文结合原理和其他的考证,深入分析下. 这里讨论的引擎是常用的In ...

  3. Mysql在可重复读事务隔离级别下怎么解决幻读的

    目录 前言 并发事务产生的问题 更新丢失 回滚丢失 覆盖丢失 脏读 不可重复读 幻读 快照读和当前读 幻读验证 快照读如何避免幻读 当前读如何避免幻读 可重复读隔离级别发生幻读情况 小结 前言 Mys ...

  4. mysql 乐观锁 脏读_mysql 丢失更新1和2、脏读、不可重复读和幻读 事务隔离级别 悲观锁 乐观锁...

    事务是现代关系型数据库的核心之一.在多个事务并发操作数据库(多线程.网络并发等)的时候,如果没有有效的避免机制,就会出现以下几种问题: ( 第一类丢失更新 A事务撤销时,把已经提交的B事务的更新数据覆 ...

  5. mysql系列:加深对脏读、脏写、可重复读、幻读的理解

    关于相关术语的专业解释,请自行百度了解,本文皆本人自己结合参考书和自己的理解所做的阐述,如有不严谨之处,还请多多指教. **不可重复读的重点是修改: **同一事务,两次读取到的数据不一样. 幻读的重点 ...

  6. MySQL事务(脏读、不可重复读、幻读)

    1. 什么是事务? 是数据库操作的最小工作单元,是作为单个逻辑工作单元执行的一系列操作:这些操作作为一个整体一起向系统提交,要么都执行.要么都不执行:事务是一组不可再分割的操作集合(工作逻辑单元): ...

  7. mysql 脏读 不可重复读 幻读_mysql事务隔离级别/脏读/不可重复读/幻读详解

    一.四种事务隔离级别 1.1read uncommitted 读未提交 即:事务A可以读取到事务B已修改但未提交的数据. 除非是文章阅读量,每次+1这种无关痛痒的场景,一般业务系统没有人会使用该事务隔 ...

  8. 数据库隔离级别解决脏读、不可重复读、幻读

    一.脏读.不可重复读.幻读 脏读 读取了未提交的事务. 事务A读取了事务B中尚未提交的数据.如果事务B回滚,则A读取使用了错误的数据. 解决:如果一个事务在读的时候,禁止读取未提交的事务. 不可重复读 ...

  9. MySQL的事务总结(事务特性,隔离级别,脏读,不可重复读,幻读,常见问题)

    MySQL的事务总结(事务四大特性,隔离级别,脏读,幻读) MYSQL官网:https://dev.mysql.com/doc/refman/8.0/en/innodb-transaction-iso ...

最新文章

  1. keyshot渲染图文教程_KeyShot渲染基础教程,教你五个步骤实现焦散线效果
  2. python英文文献_python英文文献
  3. Captcha插件后门分析和修复
  4. SpringBoot —— Bean的注入方式
  5. 深入浅出强化学习_皋兰县举办科级干部学习贯彻党的十九届五中全会精神培训班...
  6. 紫光展锐【软件工程师】面经
  7. 001机器学习深度学习简介
  8. 【负载观测】永磁同步电机的负载观测及前馈补偿
  9. Java 线程分段查询数据,再整合排序
  10. Android FTP Client 实现
  11. urllib.request.urlopen()出现的程序超时假死问题
  12. 将html导出为PDF文件(为PDF加盖章)
  13. 华硕支持2003服务器主板,驱动天空 - 品牌主板 - 服务器主板 SERVER - 华硕服务器主板...
  14. 利用Java程序统计彩票双色球中一等奖究竟有多难
  15. 华为鸿蒙系统手机会卡吗,鸿蒙系统的手机,会像安卓一样越来越卡吗?
  16. Cathy Zhang automation test
  17. 美元人民币汇率API
  18. 新唐NUC972 ARM9 内置DDR2 最大128MB 11路UART 电表 集中器 充电桩
  19. linux 配置 Apache mysql php最新版
  20. 「 机器人学 」“姿态角 roll、yaw、pitch”讲解

热门文章

  1. jmeter下载地址、环境配置以及安装完整教程
  2. 戴尔DELL 笔记本 改WIN7 BIOS 设置详解
  3. 请把Camera hold住 - Android高通平台调试Camera驱动全纪录
  4. 用UltraISO制作Ubuntu18.04 U盘启动盘
  5. SpringBoot前后端分离参数传递方式总结
  6. 【Seq2Seq】卷积序列到序列学习
  7. 数据宝高速大数据为中小微物流企业及保险公司提供双向赋能
  8. CSDN技术网摘使用指南
  9. Iconfot阿里妈妈-css高级应用
  10. zz带领团队发挥最大潜能的10个技巧