我们这里主要说的是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是如何找到对应的数据的。
- 首先获取事务的id
- 创建readview
- 查询得到的数据与readview中的事务版本号进行对比。
- 如果不符合readview中的规则,那么就会去找undo log中找历史版本数据
- 最后返回符合readview规则的数据
查找历史版本就是根据那个回滚指针依次查找。
那么mvcc在可重复读和读已提交这两个隔离级别下有什么不一样呢?
在读已提交下,一个事务中每一个select语句都会创建一个readview,每一个readview不一样。而在可重复读下只会创建一个readview,每一次select语句都用的是一个readview。所以这也是为什么可重复读解决了不可重复读和幻读。
