事务隔离是如何实现的?(着重讲mvcc)

119 阅读6分钟

我们这里主要说的是InnoDB中的读写操作,就是一个事务在读,一个事务写的情况。读读的情况呢没什么说的,也不需要任何操作。写写的操作呢,我们就是对数据进行加x锁了。

我们着重说读写的操作。我们首先回顾一下事务的隔离级别。一:读未提交。二:读已提交。三:可重复读。四:串行化。那么这四种分别是如何实现的呢?我们就以从这儿开始说。

一:读未提交。这种事务隔离级别只能解决脏写问题。也就是只实现了加锁机制。InnoDB中默认使用的行锁是临建锁(next-key lock)也是记录锁和间隙锁的结合。 (我们 这里不详细讲锁了,锁这章太复杂了。我讲不清。不过我可以推荐你们去尚硅谷康师傅的mysql,讲的确实好。链接我放在这儿了,有兴趣的可以看看。www.bilibili.com/video/BV1iq…

二:读已提交。这种事务隔离级别可以解决脏读问题。这里不仅用到了加锁机制。还用到了一项重要的技术。mvcc(Multiversion concurrency control)翻译过来就是多版本并发操作。这个我们下面详细讲。

三:可重复读。这种事务隔离级别除了解决脏读问题,还可以解决不可重复读,当然也解决了幻读。可能同学疑惑就来了。之前不是说之解决了不可重复读吗,怎么又解决了幻读。 主要就是我们底层用到了mvcc。

既然可重复读就可以解决幻读了,我们就不说串行化了。

MVCC(多版本并发控制)

事务的隔离级别的实现主要就是MVCC这个东西了。那我们来看看这个MVCC究竟是个啥,到底是如何解决那些问题的。

MVCC的核心组成是什么? 什么是MVCC? 咱们用一句话给他整明白,即简洁,又能让他觉得你牛逼。 MVCC = 隐藏字段 + undo log + readview。对就这句话。 让你升华。 别慌,接下来可能会问你这三个分别是啥,以及mvcc工作机制。 我们依次说明。

隐藏字段

隐藏字段: 我们要想知道这个东西有个前置知识,是关于一条数据内部到底有哪些东西。这个不是我们要讲的所以不赘诉。(链接在这,有兴趣就看看www.bilibili.com/video/BV1iq…)在我们存储一条数据的时候我们不光会存储数据,我们还会存储其他数据。我们这里只讲相关的两个。

  • trx_id: 事务id。就是当每次一个事务对聚簇索引上的一天数据进行改动时,都会把该事务的id给这条数据的trx_id。
  • roll_pointer: 回滚指针。当事务对每次对一条数据进行改动时,都会把旧的版本写到undo log中。而这个字段就是把旧版本按照顺序串起来。可以又最新的数据根据这个字段找到以前的版本。

undo log

看下图再结合隐藏字段,我们就可以很好的理解undo log了。

readview

在mvcc机制中,当多个事务对同一条数据进行操作时,会产生多个历史版本的记录在Undo log。如果一个事务想要查询该数据时,需要读取哪个版本的记录呢?这时候就是我们的readview来决定了。

我们现在来回忆下mvcc解决什么问题。解决脏读,解决不可重复读,解决幻读。 解析来说就是,1.就是不能读其他事务还未提交的数据。2.在我这个事务中每次的查询结果不能被其他已经提交的事务所影响。

欧克 知道了mvcc解决的问题,我们就来看看readview是如何进行判断读谁的。

我们首先要来首先清楚readview的内部一些结构。

  • creator_trx_id:创建这个事务的id。
  • trx_ids:表示在生成readview时当前系统中活跃的事务id列表(解释下活跃的事务:就是还没有提交的事务)
  • up_limit_id:活跃事务中的最小的id。
  • low_limit_id:系统中事务id最大的值 还要+1。

判断规则就是根据这四个字段进行判断的。讲判断规则前我们说一个点。事务id是根据顺序从小到大分配的。从1开始。 这里有个特殊点,当一个事务中只有select操作,那么我们这个事务的id就是0。

判断规则:

1.当creator_trx_id 和trx_id一样时,此时是可读的。此时就是在同一个事务中自己对一条数据进行写操作,然后再对其读。这样当然是可以的。

2.当trx_id小于up_limit_id,此时是可读的。由于活跃事务中最小的事务id都比trx_id大,说明操作这条数据的事务已经提交了。所以也是可读的。

3.当trx_id在up_limit_id和low_limit_id之间。那么就会判断判断trx_id是否在trx_ids中。如果在说明这条数据的事务还未提交,不可读。那么如果没有在trx_ids中就说明事务已经提交,那么就是可读的。

4.当trx_id比low_limit_id相等或者大于它。此时是不可读,说明这条数据的事务是后来创建的,自然不可读。

以上就是判断规则。

MVCC执行流程

规则说完我们来说下mvcc的执行流程吧。当执行一条查询语句时,mvcc是如何找到对应的数据的。

  1. 首先获取事务的id
  2. 创建readview
  3. 查询得到的数据与readview中的事务版本号进行对比。
  4. 如果不符合readview中的规则,那么就会去找undo log中找历史版本数据
  5. 最后返回符合readview规则的数据

查找历史版本就是根据那个回滚指针依次查找。

那么mvcc在可重复读和读已提交这两个隔离级别下有什么不一样呢?

在读已提交下,一个事务中每一个select语句都会创建一个readview,每一个readview不一样。而在可重复读下只会创建一个readview,每一次select语句都用的是一个readview。所以这也是为什么可重复读解决了不可重复读和幻读。