MySQL事务(二)事务隔离的实现原理:一致性读

今天咱们来学习一下MySQL的事务隔离是如何实现的。若是你对事务以及事务隔离级别还不太了解的话,这里左转html

好的,下面正式进入主题。事务隔离级别有4种:读未提交、读提交、可重复读和串行化。首先咱们来讲一下读未提交和串行化。数据库

  • 读未提交:性能最好,由于不加锁,因此能够理解为没有隔离。
  • 串行化:读加共享锁,其余事务可并发读,但不能写;写加排他锁,其余事务不能并发写也不能并发读。

这两种方式要么啥都无论,并发性能最好,但也最多问题;要么管得很严,没法并发处理,实现简单。数组

另外两种,读提交和可重复读,的实现方式就有考究了。并发

可重复读

首先咱们来看一下可重复读是如何实现的。性能

在可重复读隔离级别下,事务在启动的时候就“拍了个快照”,而且这个快照是基于整个库的。而“快照”在计算机里是拷贝了一份当前的副本文件,但在数据库并发访问场景下,不可能真的拷贝一份数据副本。学习

实际上,这个快照是基于InnoDB在实现MVCC时用到的一致性读视图来实现的。日志

MVCC的全称是“多版本并发控制”。这项技术使得InnoDB的事务隔离级别下执行一致性读操做有了保证,换言之,就是为了查询一些正在被另外一个事务更新的行,而且能够看到它们被更新以前的值。这是一个能够用来加强并发性的强大的技术,由于这样的一来的话查询就不用等待另外一个事务释放锁。这项技术在数据库领域并非广泛使用的。一些其它的数据库产品,以及MySQL其它的存储引擎并不支持它。htm

如何实现“快照”

InnoDB里面每一个事务有一个惟一的事务ID,叫做transaction id。它是在事务开始的时候向InnoDB的事务系统申请的,是按申请顺序严格递增的。blog

数据表中的一行记录,其实可能有多个版本(row),每一个版本都有本身的row trx_id。如图1所示:事务


图1 行状态变动图

图中虚线框里是同一行数据的4个版本,当前最新版本是V4,k的值是22,它是被transaction id为25的事务更新的,所以它的row trx_id是25。

其实除了最新版本V4外,其余三个版本其实是不存在的,它们是由undo log和最新版本数据计算获得的。其中undo段就是图中的虚线箭头的U一、U二、U3,例如V1版本就是根据V4依次执行U三、U二、U1计算获得的。

undo log是回滚日志,保存的是逻辑格式的日志,可用于事务回滚,也能够用于MVCC。

按照可重复读的定义,一个事务启动的时候,可以看到全部已经提交的事务结果。但以后在这个事务执行期间,其余事务的更新对它不可见。

InnoDB为每一个事务构成一个数组,用来保存这个事务启动瞬间,当前正在“活跃”的全部事务ID。“活跃”指启动了但尚未提交。

数组里事务ID的最小值记为低水位,当前系统里面已经建立过的事务ID的最大值加1记为高水位。这个视图数组加高水位就组成了当前事务的一致性视图(read-view)。

而数据版本的可见性规则,就是基于数据的row trx_id和这个一致性视图的对比结果获得的。

对于当前事务的启动瞬间来讲,一个数据版本的row trx_id有如下几种可能:

  • 若是落在绿色部分,表示这个版本是已经提交的事务或者是当前事务本身生成的,这个数据是可见的。
  • 若是落在红色部分,表示这个版本是由未来启动的事务生成的,不可见。
  • 若是落在橙色部分,就有两种状况:
    • 若row trx_id在数组中,表示这个版本是由还没提交的事务生成的,不可见。
    • 若row trx_id不在数组中,表示这个版本是已经提交了的事务生成的,可见。

好比图1中的数据来讲,若是有一个事务,它的低水位是18,那么当它访问这一行数据时,就会从V4经过U3计算出V3,因此在它看来,这一行的值是11。

在更新时如何使用一致性读


图3 示例1

咱们来看示例1,若是事务B在事务C更新以前查询,这个查询返回值是1。可是当它要去更新数据时,就不能在历史版本上更新了,不然事务事务C的更新就会丢失。

这里就用到一条规则:更新数据都是先读后写,而这个读只能是读当前的值,称为“当前读”(current read)。

所以事务B更新时,当前读拿到的数据是(1, 2),更新后是(1, 3),而且row trx_id是101。

事务B后续查询时,看到最新数据的版本号是101,而本身也是101,就直接返回,获得的k值是3。


图4 示例2

咱们再来看示例2,示例2与示例1的区别在于,事务C'在事务B的写读操做后提交。

事务C'在提交前对行加写锁。而事务B是当前读,并且必需要加锁,所以被锁住了,必须等到事务C'释放这个锁,才能继续它的当前读。

到这里,把一致性读、当前读和行锁串起来了。

小结

本节问题,事务的可重复读隔离级别是如何实现的?

可重复读的核心就是一致性读;而事务更新数据的时候,只能用当前读。若是当前的记得的行锁被其余事务占用的话,就须要进入锁等待。

读提交

读提交的实现方式跟可重复读相似,它们最主要的区别是:

  • 在可重复读隔离级别下,只须要在事务开始的时候建立一致性视图,以后事务里的其余查询都共用这个一致性视图;
  • 在读提交隔离级别下,每一个语句执行前都会从新算出一个新的视图。

参考资料

相关文章
相关标签/搜索