MySQL专题-多版本并发控制 MVCC&read committed 隔离级别

上节回顾

上篇记录了我对MySQL事务隔离级别read uncommitted的理解。这篇记录我对MySQL事务隔离级别read committed&MVCC的理解。

前言

可以很负责任的跟大家说,MySQL中的此隔离级别不单单是通过加锁实现的,实际上还有可重复的读隔离级别,其实这两个隔离级别效果的实现还需要一个辅助,这个辅助就是MVCC- 多版本并发控制,但其实它又不是严格意义上的多版本并发控制,是不是很懵,没关系,我们一一剖析。

目录

单纯加锁是怎么实现read committed的?

真实的演示情况是什么样子的?

MVCC实现原理?

对于InnoDB MVCC实现原理的反思

参考

1.单纯加锁是怎么实现read committed的?

从此隔离级别效果入手:事务只能读其他事务已提交的记录。数据库事务隔离级别的实现,InnoDB支持行级锁,写时加的是行级排他锁(X锁),那么当其他事务访问另一个事务正在更新(除选择操作外其他操作本质上都是写操作)的同一条记录时,事务的读操作会被阻塞。所以只能等到记录(其实是索引上的锁)上的排他锁释放后才能进行访问,也就是事务提交的时候。这样确实能实现read commited隔离级别效果。数据库这样做确实可以实现事务只能读其他事务已提交的记录的效果,但是这是很低效的一种做法,为什么呢?因为对于大部分应用来说,读操作是多于写操作的,当写操作加锁时,那么读操作全部被阻塞,这样会导致应用的相应能力受数据库的牵制。

2.真实的演示情况是什么样子的?

看如下操作:

2.1开启两个客户端实例,设置事务隔离级别为read committed,并各自开启事务。

2.2客户端1做更新操作:

结果如下图所示:

2.3客户端2做查询操作:

结果如下所示:

这时估计你有疑问了,正在被客户端1 upate的记录,客户端2还能无阻塞的读到,而且读到的是未更改之前的数据。那就是InnoDB的辅助打得好,因为内部使用了MVCC机制,实现了一致性非阻塞读,大大提高了并发读写效率,写不影响读,且读到的事记录的镜像版本。

下面开始介绍MVCC原理。

3.MVCC实现原理

网上对MVCC实现原理的讲述五花八门,良莠不齐。包括“高性能MySQL”对MVCC的讲解只是停留在表象,并没有结合源码去分析。当然绝大多数人还是相信这本书的,从来没有进行深剖,思考。如下是“高性能MySQL”对MVCC实现原理的描述:

就是这本书,蒙蔽了真理,害人不浅。

我们还是看源码吧:

3.1记录的隐藏列

其实有三列

3.2 MVCC实现的依赖项

MVCC在mysql中的实现依赖的是undo log与read view。

3.3撤消日志

undo log是为回滚而用,具体内容就是复制事务前的数据库内容(行)到undo buffer,在适合的时间把undo buffer中的内容刷新到磁盘.undo buffer与redo buffer一样,也是环形缓冲,但当缓冲满的时候,undo buffer中的内容会也会被刷新到磁盘;与redo log不同的是,磁盘上不存在单独的undo log文件,所有的undo log均存放在主ibd数据文件中(表空间),即使客户端设置了每表一个数据文件也是如此。

我们通过行的更新过程来看下撤回日志是如何形成的?

#### 3.3.1行的更新过程下面演示下事务对某行记录的更新过程:1。初始数据行F1~F6是某行列的名字,1~6是其对应的数据。后面三个隐含字段分别对应该行的事务号和回滚指针,假如这条数据是刚INSERT的,可以认为ID为1,其他两个字段为空。

2.事务1更改行的各字段的值

当事务1更改行的值时,会进行如下操作:用排锁锁定该行记录redo log把该行修改前的值复制到撤消日志,即上图中下面的行修改当前行的值,填写事务编号,使回滚指针指向撤消日志中的修改前的行

3.事务2修改该行的值

与事务1相同,此时undo log,中有有两行记录,并且通过回滚指针连在一起。

4.read view判断当前版本数据项是否可见

在innodb中,创建一个新事务的时候,innodb会将当前系统中的活跃事务列表(trx sys-> trxlist)创建一个副本(阅读视图),副本中保存的是系统当前不应该被本事务看到的其他事务id列表。当用户在这个事务中要读取该行记录的时候,innodb会将该行当前的版本号与该阅读进行比较。

具体的算法如下:

设该行的当前事务id为trxid0,read view中最早的事务id为trxid1,最迟的事务id为trxid2。

如果trxid0

如果trxid0> trxid2的话,那么表明该行记录所在的事务在本次新事务创建之后才开启,所以该行记录的当前值不可见。跳到步骤5。

如果trxid1

从该行记录的DBROLLPTR指针所指向的回滚段中取出最新的撤销日志的版本号,将它赋值该trxid0,然后跳到步骤2。

将该可见行的值返回。

需要注意的是,新建事务(当前事务)与正在内存中提交的事务不在活跃事务链表中。

对应代码如下:

函数:读取视图看到trxid。读取视图中保存了当前全局的事务的范围:【下限id,上限id】1.当行记录的事务ID小于当前系统的最小活动id,就是可见的.if( trxid uplimitid)2.当行记录的事务ID大于当前系统的最大活动ID,就是不可见的。如果(TRXID> =查看- >低限ID)3。当行记录的事务ID在活动范围之中时,判断是否在活动链表中,如果在就不可见,如果不在就是可见的。对于(I = 0; I

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20181029A1S2WX00?refer=cp_1026
  • 腾讯「云+社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。

扫码关注云+社区

领取腾讯云代金券