职业生涯规划

我身边的高T,问了Java面试者这样的问题,他人傻了

2021-02-26  本文已影响0人  傻姑不傻

本文地址:https://segmentfault.com/a/1190000039248405

本文作者:京东科技开发者

前言

今天来分享一个京东面试真题,这也是我前阵子听我工位旁边高T(高,实在是高)面试候选人的时候问的一个问题,他问,你能说说 MySQL的事务吗?MVCC有了解吗?话不多说,本文就深度解析一下MySQL事务及MVCC的实现原理。

事务定义及四大特性

事务是什么?

事务的四大特性(简称ACID)

事务中常见问题

隔离级别——产生问题的原因

多个事务互相影响,并没有隔离好,就是我们刚才提到的事务的四大特性中的 隔离性(Isolation) 出现了问题 事务的隔离级别并没有设置好,下面我们来看下事务究竟有哪几种隔离级别

我们来看个例子,更加直观的了解这四种隔离级别和上述问题脏读,不可重复读,幻读的关系

下面我们讨论下当事务处于不同隔离级别情况时,V1,V2,V3分别是什么不同的值吧

MVCC原理

MVCC(Multi-Version Concurrency Control)多版本并发控制,是数据库控制并发访问的一种手段。

特别要注意MVCC只在 读已提交(RC)可重复度(RR) 这两种事务隔离级别下才有效是 数据库引擎(InnoDB) 层面实现的,用来处理读写冲突的手段(不用加锁),提高访问性能

MVCC是怎么实现的呢?它靠的就是版本链一致性视图

1. 版本链

那么这个版本链又是如何形成的呢,每条数据又是靠什么链接起来的呢?

其实是这样的,对于InnoDB存储引擎的表来说,它的聚簇索引记录包含两个隐藏字段

假如说当前数据库有一条这样的数据,假设是事务ID为100的事务插入的这条数据,那么此条数据的结构如下

后来,事务200,事务300,分别来修改此数据

所以此时的版本链如下

我们每更改一次数据,就会插入一条undo日志,并且记录的roll_pointer指针会指向上一条记录,如图所示

  1. 第一条数据是小杰,事务ID为100
  2. 事务ID为200的事务将名称从小杰改为了A
  3. 事务ID为200的事务将名称从A又改为了B
  4. 事务ID为300的事务将名称从B又改为了C

所以串成的链表就是 C -> B -> A -> 小杰 (从最新的数据到最老的数据)

2. 一致性视图(ReadView)

需要判断版本链中的哪个版本是是当前事务可见的,因此有了一致性视图的概念。其中有四个属性比较重要

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的

下面咱们一起来举个例子实战一下。

RR与RC和MVCC的例子实战

假如说,我们有多个事务如下执行,我们通过这个例子来分析当数据库隔离级别为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

读已提交(RC)与MVCC

每次读的时候,ReadView(一致性视图)都会重新生成

  1. 当T1时刻时,事务100修改名字为A
  2. 当T2时刻时,事务100修改名字为B
  3. 当T3时刻时,事务200修改名字为C
  4. 当T4时刻时,事务300开始读取名字

同颜色代表是同一事务内的操作

当前最近的一条数据是,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前提交了,此数据可以被读到。所以读取的数据就是小杰。

分析完第一个读,我们继续向下分析

  1. 当T5时刻时,事务100提交
  2. 当T6时刻时,事务300将名字改为D
  3. 当T7时刻时,事务400读取当前数据

此时 (重新生成一致性视图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。

分析完第二个读,我们继续向下分析

  1. 当T8时刻时,事务200将名字改为E
  2. 当T9时刻时,事务200提交
  3. 当T10时刻时,事务300读取当前数据

此时 (重新生成一致性视图ReadView

当前事务id是200,200<min_trx_id ,代表生成这个数据的事务已经在生成ReadView前提交了,此数据可见,所以读出的数据就是E。

当隔离级别是读已提交RC的情况下,每次读都会重新生成 一致性视图(ReadView)

T4时刻 事务300读取到的数据是小杰

T7时刻 事务400读取到的数据是B

T10时刻 事务300读取到的数据是E

可重复读(RR)与MVCC

所以对于事务300来讲,它分别在T4和T10的时候,读取数据,但是它的一致性视图,用的永远都是第一次读取时的视图,就是T3时刻产生的一致性视图

RR和RC的版本链是一样的,但是判断当前数据可见与否用到的一致性视图不一样

在此可重复读RR隔离级别下,

  1. T4时刻时事务300第一次读时的分析和结果与RC都一样,可以见上文分析与结果
  2. T7时刻时事务400第一次读时的分析和结果与RC都一样,可以见上文分析与结果
  3. T10时刻时事务300第二次读时的一致性视图和第一次读时的一样,所以此时到底读取到什么数据就要重新分析了

此时 (用的是第一次读时生成的一致性视图ReadView

此时的版本链是

当前数据的事务id是200,数据是E,在当前事务活跃列表里面,所以数据不可见,根据回滚指针找到上个版本,发现事务id是300,当前事务也是300,可见,所以读取的数据是D

当隔离级别是可重复读RR的情况下,每次读都会用第一次读取数据时生成的一致性视图(ReadView)

T4时刻 事务300读取到的数据是小杰

T7时刻 事务400读取到的数据是B

T10时刻 事务300读取到的数据是D

上一篇 下一篇

猜你喜欢

热点阅读