事务隔离级别的简介与MVCC的实现
Posted 刘俊岐.
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了事务隔离级别的简介与MVCC的实现相关的知识,希望对你有一定的参考价值。
大家对mysql事物的隔离级别一定很清楚,分别有:
- 读未提交
- 读已提交
- 可重复读
- 串行化
而mysql默认的隔离级别是可重复读,那么今天我就给大家说说可重复读的实现原理。
可重复的的意思就是在在一个事务开始过后,无论是否其他事务对某一行或某几行进行了修改,只要本事务没有对其修改,那么这些行的数据在本事务中永远是不变的。所以在mysql内部究竟是如何实现这一机制的呢?实现这一机制是否会带来其他的问题呢?
首先我们关注下这个事务执行的时序图
事务A | 事务B |
---|---|
开启事务 | 开启事务 |
查询得到值C | |
查询的到值C | |
将值C改为值D | |
查询得到值E | |
提交事务B | |
查询得到值F | |
提交事务A |
我们可以看下,当事务的隔离级别分别是读未提交,读已提交,可重复读时候值A、值B、值D究竟是多少。
- 当事务隔离级别是读未提交的时候,事务B执行修改值C值为D后,事务A可以立即看到,所以值E和值F都等于值D
- 当事务隔离级别是读已提交的时候,当事务B未提交的时候,我们还看不到值D,所以值E等于值C,直到事务B提交之后,事务A再次查询的时候,会看到修改后的值D
- 当事务隔离级别是可重复读的时候,事务A自始至终都看不到事务B对值C的修改,所以值E和值F都等于值C
可是数据库是如何实现这种机制的呢?在数据库里面,会创建一个视图,取值的时候视图的逻辑结果为准,当隔离级别是可重复读时候,这个视图是开启事务时创建的,而当隔离级别是读已提交的时候,这个视图是在每个sql语句提交的时候创建的。
我们可以通过参数transaction-isolation
设置mysql的隔离级别
事务隔离级别的具体实现
这里我详细说明下可重复读的实现原理,为了支持事务的回滚操作,在mysql中设置了undo log
,实际上在每一条记录更新的时候都会记录一个回滚的操作,记录上的最新值都可以通过回滚操作获得之前状态的值。那么undo log中记录的到底是什么呢?下面我举一个例子,将一个值从1依次改到2、3、4后,mysql中的undo log是什么样子的。
当我们在修改这个记录的同时,不同时刻开启的事务就会看到不同的read-view,也就是说同样的一条记录在多个事务中可能有多个不同的版本,这也就是MVCC
的原理。
也许你会发现如果这样下去,我们的undo log会越来越长,这样下去肯定不行。在InnoDB的设计中,等到这些undo log不需要的时候会进行删除,那怎么判断什么时候是不需要呢?答案就是当前系统里没有比这个回滚日志更早的read-view的时候
。
这是我们会想到,如果我们使用了一个很长的事务,这时这个事务的undo log会非常大,占用很大的磁盘空间,所以我们尽量也不要使用长事务。
MVCC的实现
既然提到了刚刚提到了MVCC那我们就不得不详细说明下。
MVCC(多版本并发控制)存在的目的就是对数据库的并发访问提供一种实现方式。
在我们刚刚说到的可重复读隔离级别中,就是在事务启动的时为整个库拍了一个“快照”,但是这个快照肯定不是拷贝数据得来的,如果是拷贝数据的话,那得多慢呀!MySQL肯定不会使用这种方式,那这个“快照”在MVCC下是怎么工作的呢?
在InnoDB里面,每个事物启动的时候,InnoDB都会赋给其一个事务ID(transation id)。同时每行数据都有其数据的版本(row trx_id),而这个数据的版本就是更新这行数据事务的版本,并且老的数据会保留,并且会有方法拿到他(也就是通过上述的undo log)。
下图表示了undo log和每行记录的关系
弄清楚了row trx_id 和 transation id 以及undo log的概念以及他们之间的关系后,我们再来说下InnoDB怎样定义哪些数据在当前的事务中是可见的。也就是如何实现可重复读中的要求:以当前事务启动的时刻为准,一个数据版本在当前事务启动之前生成的就可见,否则就不可见,我需要找到其上一个版本。
在MySQL内部的实现上,在每一个事务启动的时候,会为每一个事务构建一个数组,来保存当前启动了还没提交的事务。同时在这个数组中,事务ID的最小值就记为低水位
,事务ID的最大值+1就记为高水位
。
所以,规则如下:
- 如果一个数据的版本落在绿色部分,可见
- 如果一个数据落在黄色部分,分两组情况
- 这个数据的row trx_id在数组中,不可见
- 这个数据的row trx_id不在数组中,可见
- 如果数据落在红色部分,不可见
所以,当前事务可见的值不是最新的数据时,就需要通过row trx_id配合undo log还原当前事务快照的值。这样也就实现了MySQL秒级创建快照的能力。
但是对于更新过程可能会有点区别,我们看下下面这个时序图
序号 | 事务A | 事务B |
---|---|---|
T1 | begin; | |
T2 | select c from T where id = 1 | |
T3 | update T set c = c+1 where id = 1 | |
T4 | update T set c = c+1 where id = 1 | |
T5 | select c from T where id = 1 |
假设id=1这行c的记录初始值为0,那么执行事务A T5的返回值应该是多少呢。
答案是2,这是因为update语句是当前读,不是快照读,要获取当前行数据最大的row trx_id所对应的结果
。
以上是关于事务隔离级别的简介与MVCC的实现的主要内容,如果未能解决你的问题,请参考以下文章