Hi,大家好!我是白日梦。

今天我要跟你分享的话题是:“MySQL是如何根据undo log 链条实现read view机制的?谈谈看”

一、事物的隔离级别与MVCC?

MySQL单进程多线程的数据库软件,在事务的并发操作中可能会出现脏读,不可重复读,幻读。

MySQL支持的四种事务隔离级别如下:

  • Read uncommited

    简单来说就是:事务A可以读到事务B未commit的数据。这种情况也被叫做脏读。

  • Read commited

    简单来说就是:事务A可以读到事务B已经commit的数据。

  • Serializable

    在该级别下,写会加写锁、读会加读锁,除了读读不互斥,其他组合都互斥,因此可以保证事务串行化顺序执行,可以避免脏读、不可重复读与幻读。

  • Repeatable read

    如下图:可重复读要求事物A两次 select 查询出来的结果是一样的,即使中间事物B将id=1的行给修改了,也要保证事物A再读取时,读到的结果也得和第一次读到的结果相同。

但是可重复读存在幻读读问题,比如事物A开启后按某个范围X读取一次(事物未提交),这时其他事物在该范围X内插入了新的数据,事物A再读时就会将新插入的数据读取出来,当然在MySQL的RR隔离级别下不会再出现这种幻行的问题。

问题的解决得益于:MVCC多版本并发控制的快照读和next-key lock 当前读。

二、Repeatable Read是如何实现的

以RR隔离级别为例:

你可以像下面这样看一下你的MySQL默认使用的什么隔离级别:

MVCC多版本并发控制也被称为快照读,在RR的隔离级别下,当事物开启时会创建一个视图(Read View),其实这个视图就是所谓的快照。在整个事物存在的期间,一直会使用这个视图。

下面看一个九个步骤的小实验:

上图中的右部分的会话中begin之后,就会创建读视图,所以它的多次select使用的是同一个视图,所以结果都是一样的。即使数据中途被左边的事物更改了,它也没有受到影响。

再结合视图去理解这个过程。

当你执行begin开启事物之后,MySQL会拍下像下图这样的快照:

上图中的trx_ids中记录着MySQL中活跃的且未提交的事物。

假设有事物A、事物B擦不多在同一时刻开启,那这两个事物会分别得到如下的视图。

在RR的隔离级别下,事物一开启就会得到上图那样的ReadView,并且只要事物不提交这个ReadView就一直有效。

就上图来说:

在事物A的视图中,它的事物ID=61,此时活跃的事物集合是[61、62],活跃的事物ID中最小的事物id是它本身。下一个事物id应该是63。

在事物B的视图中,它的事物ID=621,此时活跃的事物集合是[61、62],活跃的事物ID中最小的事物id是61。下一个事物id应该是63。

先让事物A尝试去读取name列的数据。

它会发现的这行数据的Data_TRX_ID=60,通过和trx_ids对比发现这个事物ID不在活跃的事物id集合trx_ids中,并且小于它本身的60。说明:在事物A开启之前,事物ID=60的事物早就提交过了。所以事物A能直接这行数据name = tom。

然后事物B通过update语句尝试去修改这行数据,想将name 改成 jetty。这时MySQL会记录相应的undo log,并以链表的方式串联起来,于是我们会得到下图:

你可以看到上图中,由于事物B将name改成jerry,导致多出一条undo log。这条undo对应的事物ID=事物B的事物ID = 62。并且通过一个指针执向它的上一个undo log记录。

这时如果事物A重新去读,首先它会读取到的记录是name = jerry,但是它也会发现该记录的trx_id = 62 , 比自己的61还大,并且比下一个事物ID63小。说明:它读到记录其实是和自己同时开启的事物修改后的产物,这时他就会沿着undo log链条往前找,直到找到第一个trx_id等于或者小于自己事物ID的记录为止。所以事物A再一次读取到trx_id = 60的记录。

这也就是所谓的快照读机制。

另外需要注意的是:就上例来说,在RR的隔离级别下,确实能保证事物A每次读取出来的结果都是一样的,而且在事物B将其修改后,事物A依然能读取出name = tom。但是这时name=tom真的只是个快照,本质上它已经可以算是不存在是数据了。

本文是MySQL专题第15篇,全文近100篇(公众号首发)

本文是第15篇,全文近100篇,点击查看目录

三、Read Commited是如何实现的:

在RR隔离级别下,当事物一开始视图就会被创建出来,并且一直到该事物提交该视图都有效。

在Read Commited隔离级别,每次select 都会创建一个新的视图。

还是使用这个例子:假设事物A和事物B并发开启,并且各自得到了图中的ReadView。然后很快,事物B就将数据name = tom改成了name = jerry(未提交)。那这时事物A去select会检索出什么结果呢?

事物A检索过程:事物A首先会沿着undo log链条从头开始找,于是它首先找到name = jerry的列。但是它也发现该列的trx_id = 62 不但比自己的事物ID60大,而且还在trx_ids这个活跃事物列表中,说明name = jerry是被和自己差不多同时开启的其他事物更改的。它自然也就读不到。

紧接着事物B提交事物,然后事物A重新select会开启一个新的视图,得到如下图:

当事物A沿着undo log链条往下查找时,他发现首先发现的name = jerry的行的trx_id是62,竟然比自己的事物ID61还大,但是进一步发现,这个事务ID62并不在trx_ids中。说明,这个其实是已经被提交了的数据,那直接就意味着其实自己是允许读出这条数据的。这也就是所谓的读已提交机制。

### 四、长事物的风险

其实文章看到这里,长事物有什么风险你应该也可以感觉出来了。

事务迟迟不结束,就意味着它随时可能会访问到数据库中任何数据,所以只要是它们可能用的回滚记录,数据库都得为他们保留着。所以事物越长,相应的他对应的视图也就越大。

上一篇文章中白日梦有和大家介绍过 undo log 默认存放在共享表空间文件中,同时在SQL5.6 MySQL5.7在也允许你将undo log拿到单独的表空间中去,但是不论怎样,undo log总会以真实存在的文件的形式存在于磁盘上,当然了MySQL5.7的undo truncate机制 结合purge线程可以将不需要的undo log清除掉,为undo log文件瘦身,但是在这之前undo log的体量会不断的增大,再加上大量的长事物,很可能会将磁盘打爆。

另外长事物大概率是update等DML导致的,这种DML是会持有行锁的。谁也不能保证长时间不释放锁不会导致数据库被拖垮。

本文是MySQL专题第15篇,全文近100篇(公众号首发)

本文是第15篇,全文近100篇,点击查看目录

最新文章

  1. 纯css3手机页面图标样式代码
  2. jQuery之Ajax--底层接口
  3. 查看mysql语句运行时间
  4. 针对不同包之间的action跳转,怎么配置?
  5. 基于Redis的CustomerSessionProvider(一)
  6. ios系统下的QQ浏览器jquert的BUG
  7. css修炼宝典
  8. Spring还使用基于 JSR-250 注释,它包括 @PostConstruct, @PreDestroy 和 @Resource 注释
  9. 全面了解Android热修复技术
  10. JSONArray - JSONObject - 遍历 \ 判断object空否
  11. BZOJ1493 NOI2007 项链工厂 线段树模拟
  12. WPF自学入门(九)WPF自定义窗口基类
  13. 关于JavaScript的那些话
  14. HashMap和LinkedHashMap的区别
  15. QQ项目
  16. 无法打开工作组信息文件中的表 'MSysAccounts',一个十分搞笑的解决方法
  17. 线程 Thread Handler
  18. oracle 根据字段查询重复数据
  19. Matlab——GUI初涉
  20. Bayes' theorem (贝叶斯定理)

热门文章

  1. Python使用JsAPI发起微信支付 Demo
  2. adb命令如何获取appPackage和appActivity的信息
  3. uniapp微信小程序canvas绘图插入网络图片不显示
  4. Java自动化测试框架-02 - TestNG之理论到实践
  5. php 使用 phpword 操作 word 读取 word
  6. js练习题之查找数组中的位子
  7. 数据库会话数量过多,定期清理inactive会话
  8. Java读取Excel报错Unable to recognize OLE stream
  9. Linux Capabilities 入门教程:进阶实战篇
  10. Java中float浮点型变量不加F报错情况