你们好,我是来自JDL京东物流技术发展部邢焕杰, 今天来分享一个京东面试真题,这也是我前阵子听我工位旁边高T(高,实在是高)面试候选人的时候问的一个问题,他问,你能说说 MySQL的事务吗?MVCC有了解吗?话很少说,本文就深度解析一下MySQL事务及MVCC的实现原理。
多个事务互相影响,并无隔离好,就是咱们刚才提到的事务的四大特性中的 隔离性(Isolation) 出现了问题 事务的隔离级别并无设置好,下面咱们来看下事务究竟有哪几种隔离级别java
咱们来看个例子,更加直观的了解这四种隔离级别和上述问题脏读,不可重复读,幻读的关系面试
下面咱们讨论下当事务处于不一样隔离级别状况时,V1,V2,V3分别是什么不一样的值吧数据库
MVCC(Multi-Version Concurrency Control)多版本并发控制,是数据库控制并发访问的一种手段。segmentfault
- 特别要注意MVCC只在 读已提交(RC) 和 可重复度(RR) 这两种事务隔离级别下才有效
- 是 数据库引擎(InnoDB) 层面实现的,用来处理读写冲突的手段(不用加锁),提升访问性能
MVCC是怎么实现的呢?它靠的就是版本链和一致性视图并发
那么这个版本链又是如何造成的呢,每条数据又是靠什么连接起来的呢?性能
实际上是这样的,对于InnoDB存储引擎的表来讲,它的聚簇索引记录包含两个隐藏字段spa
假如说当前数据库有一条这样的数据,假设是事务ID为100的事务插入的这条数据,那么此条数据的结构以下指针
后来,事务200,事务300,分别来修改此数据日志
因此此时的版本链以下blog
咱们每更改一次数据,就会插入一条undo日志,而且记录的roll_pointer指针会指向上一条记录,如图所示
因此串成的链表就是 C -> B -> A -> 小杰 (从最新的数据到最老的数据)
须要判断版本链中的哪一个版本是是当前事务可见的,所以有了一致性视图的概念。其中有四个属性比较重要
- m_ids: 在生成ReadView时,当前活跃的读写事务的事务id列表
- min_trx_id: m_ids的最小值
- max_trx_id: m_ids的最大值+1
- creator_trx_id: 生成该事务的事务id,单纯开启事务是没有事务id的,默认为0,creator_trx_id是0。
版本链中的当前版本是否能够被当前事务可见的要根据这四个属性按照如下几种状况来判断
- 当 trx_id = creator_trx_id 时:当前事务能够看见本身所修改的数据, 可见,
- 当 trx_id < min_trx_id 时 : 生成此数据的事务已经在生成readView前提交了, 可见
- 当 trx_id >= max_trx_id 时 :代表生成该数据的事务是在生成ReadView后才开启的, 不可见
- 当 min_trx_id <= trx_id < max_trx_id 时
- trx_id 在 m_ids 列表里面 :生成ReadView时,活跃事务还未提交,不可见
- trx_id 不在 m_ids 列表里面 :事务在生成readView前已经提交了,可见
若是某个版本数据对当前事务不可见,那么则要顺着版本链继续向前寻找下个版本,继续这样判断,以此类推。
注:RR和RC生成一致性视图的时机不同 (这也是两种隔离级别实现的主要区别)
- 读提交(read committed RC) 是在每一次select的时候生成ReadView的
- 可重复读(repeatable read RR)是在第一次select的时候生成ReadView的
下面我们一块儿来举个例子实战一下。
假如说,咱们有多个事务以下执行,咱们经过这个例子来分析当数据库隔离级别为RC和RR的状况下,当时读数据的一致性视图和版本链,也就是MVCC,分别是怎么样的。
- 假设数据库中有一条初始数据 姓名是java小杰要加油,id是1 (id,姓名,trx_id,roll_point),插入此数据的事务id是1
- 尤为要指出的是,只有这个事务操做了某些表的数据后当更改操做发生的时候(update,delete,insert),才会分配惟一的事务id,而且此事务id是递增的,单纯开启事务是没有事务id的,默认为0,creator_trx_id是0。
- 如下例子中的A,B,C的意思是将姓名更改成A,B,C 读也是读取当前时刻的姓名,默认全都开启事务,而且此事务都经历过某些操做产生了事务id
每次读的时候,ReadView(一致性视图)都会从新生成
同颜色表明是同一事务内的操做
当前最近的一条数据是,C,事务200修改的,还记得咱们前文说的一致性视图的几个属性吗,和按照什么规则判断这个数据能不能被当前事务读。咱们就分析这个例子。
此时 (生成一致性视图ReadView)
当前数据的trx_id(事务id)是 200,符合min_trx_id<=trx_id<max_trx_id 此时须要判断 trx_id 是否在m_ids活跃事务列表里面,一看,活跃事务列表里面是【100,200】,只有两个事务活跃,而此时的trx_id是200,则trx_id在活跃事务列表里面,活跃事务列表表明还未提交的事务,因此该版本数据不可见,就要根据roll_point指针指向上一个版本,继续这样的判断,上一个版本事务id是100,数据是B,发现100也在活跃事务列表里面,因此不可见,继续找到上个版本,事务是100,数据是A,发现是一样的状况,继续找到上个版本,发现事务是1,数据是小杰,1小于100,trx_id<min_trx_id,表明生成这个数据的事务已经在生成ReadView前提交了,此数据能够被读到。因此读取的数据就是小杰。
分析完第一个读,咱们继续向下分析
此时 (从新生成一致性视图ReadView)
当前数据事务id是300,数据为D,符合min_trx_id<=trx_id<max_trx_id 此时须要判断数据是否在活跃事务列表里,300在这里面,因此就是还未提交的事务就是不可见,因此就去查看上个版本的数据,上个版本事务id是200,数据是C,也在活跃事务列表里面,也不可见,继续向上个版本找,上个版本事务id是100,数据是B,100小于min_trx_id,就表明,表明生成这个数据的事务已经在生成ReadView前提交了,此数据可见,因此读取出来的数据就是B。
分析完第二个读,咱们继续向下分析
此时 (从新生成一致性视图ReadView)
当前事务id是200,200<min_trx_id ,表明生成这个数据的事务已经在生成ReadView前提交了,此数据可见,因此读出的数据就是E。
当隔离级别是读已提交RC的状况下,每次读都会从新生成 一致性视图(ReadView)
- T4时刻 事务300读取到的数据是小杰
- T7时刻 事务400读取到的数据是B
- T10时刻 事务300读取到的数据是E
因此对于事务300来说,它分别在T4和T10的时候,读取数据,可是它的一致性视图, 用的永远都是第一次读取时的视图,就是T3时刻产生的一致性视图
RR和RC的版本链是同样的,可是判断当前数据可见与否用到的一致性视图不同
在此可重复读RR隔离级别下,
此时 (用的是第一次读时生成的一致性视图ReadView)
此时的版本链是
当前数据的事务id是200,数据是E,在当前事务活跃列表里面,因此数据不可见,根据回滚指针找到上个版本,发现事务id是300,当前事务也是300,可见,因此读取的数据是D
当隔离级别是可重复读RR的状况下,每次读都会用第一次读取数据时生成的一致性视图(ReadView)
- T4时刻 事务300读取到的数据是小杰
- T7时刻 事务400读取到的数据是B
- T10时刻 事务300读取到的数据是D
欢迎点击【京东科技】,了解开发者社区
更多精彩技术实践与独家干货解析
欢迎关注【京东科技开发者】公众号