大家好,我是公众号:java小杰要加油,
明天来分享一个京东面试真题,也是这是我前阵子听我旁边高T(高,实在是高)面试候选人的时候问的一个问题,他问,你能说说 mysql的事务吗? MVCC有理解吗?
- 话不多说,间接开干
事务定义及四大个性
- 事务是什么?
就是用户定义的一系列数据库操作,这些操作能够视为一个实现的逻辑解决工作单元,要么全副执行,要么全副不执行,是不可分割的工作单元。
-
事务的四大个性(简称ACID):
- 原子性(Atomicity):一个事务是一个不可分割的工作单位,事务中包含的操作要么都做,要么都不做。
- 一致性(Consistency):事务必须是使数据库从一个一致性状态变到另一个一致性状态。一致性与原子性是密切相关的。
- 隔离性(Isolation):一个事务的执行不能被其余事务烦扰。即一个事务外部的操作及应用的数据对并发的其余事务是隔离的,并发执行的各个事务之间不能相互烦扰.
- 持久性(Durability):指一个事务一旦提交,它对数据库中数据的扭转就应该是永久性的,接下来的其余操作或故障不应该对其有任何影响。
事务中常见问题
- 脏读(dirty read):就是一个A事务即使没有提交,它对数据的批改也能够被其余事务B事务看到,B事务读到了A事务还未提交的数据,这个数据有可能是错的,有可能A不想提交这个数据,这只是A事务批改数据过程中的一个两头数据,然而被B事务读到了,这种行为被称作脏读,这个数据被称为脏数据
- 不可反复读(non-repeatable read):在A事务内,屡次读取同一个数据,然而读取的过程中,B事务对这个数据进行了批改,导致此数据变动了,那么A事务再次读取的时候,数据就和第一次读取的时候不一样了,这就叫做不可反复读
- 幻读(phantom read):A事务屡次查询数据库,后果发现查问的数据条数不一样,A事务屡次查问的距离中,B事务又写入了一些合乎查问条件的多条数据(这里的写入能够是update,insert,delete),A事务再查的话,就像产生了幻觉一样,怎么忽然扭转了这么多,这种景象这就叫做幻读
隔离级别——产生问题的起因
多个事务相互影响,并没有隔离好,就是咱们方才提到的事务的四大个性中的 隔离性(Isolation) 呈现了问题 事务的隔离级别并没有设置好,上面咱们来看下事务到底有哪几种隔离级别
-
隔离级别
- 读未提交(read uncommitted RU): 一个事务还没提交时,它做的变更就能被别的事务看到
- 读提交(read committed RC): 一个事务提交之后,它做的变更才会被其余事务看到。
- 可反复读(repeatable read RR): 一个事务执行过程中看到的数据,总是跟这个事务在启动时看到的数据是统一的。当然在可反复读隔离级别下,未提交变更对其余事务也是不可见的。
- 串行化(serializable ): 顾名思义是对于同一行记录,“写”会加“写锁”,“读”会加“读锁”。当呈现读写锁抵触的时候,后拜访的事务必须等前一个事务执行实现,能力继续执行。
咱们来看个例子,更加直观的理解这四种隔离级别和上述问题脏读,不可反复读,幻读的关系
上面咱们探讨下当事务处于不同隔离级别状况时,V1,V2,V3别离是什么不同的值吧
-
读未提交 (RU): A事务能够读取到B事务批改的值,即使B事务没有提交。所以V1就是200
- V1 : 200
- V2 : 200
- V3 : 200
-
读提交(RC): 当B事务没有提交的时候,A事务不能够看到B事务批改的值,只有提交当前才能够看到
- V1 : 100
- V2 : 200
- V3 : 200
-
可反复读(RR): A事务屡次读取数据,数据总和第一次读取的一样,
- V1 : 100
- V2 : 100
- V3 : 200
-
串行化(S): 事务A在执行的时候,事务B会被锁住,等事务A执行完结后,事务B才能够继续执行
- V1 : 100
- V2 : 100
- V3 : 200
MVCC原理
MVCC(Multi-Version Concurrency Control)多版本并发管制,是数据库管制并发拜访的一种伎俩。
- 特地要留神MVCC只在 读已提交(RC) 和 可反复度(RR) 这两种事务隔离级别下才无效
- 是 数据库引擎(InnoDB) 层面实现的,用来解决读写抵触的伎俩(不必加锁),进步拜访性能
MVCC是怎么实现的呢?它靠的就是版本链和一致性视图
1. 版本链
- 版本链是一条链表,链接的是每条数据已经的批改记录
那么这个版本链又是如何造成的呢,每条数据又是靠什么链接起来的呢?
其实是这样的,对于InnoDB存储引擎的表来说,它的聚簇索引记录蕴含两个暗藏字段
- trx_id: 存储批改此数据的事务id,只有这个事务操作了某些表的数据后当更改操作产生的时候(update,delete,insert),才会调配惟一的事务id,并且此事务id是递增的
- roll_pointer: 指针,指向上一次批改的记录
- row_id(非必须): 当有主键或者有不容许为null的unique键时,不蕴含此字段
如果说以后数据库有一条这样的数据,假如是事务ID为100的事务插入的这条数据,那么此条数据的构造如下
起初,事务200,事务300,别离来批改此数据
工夫T | trx_id 200 | trx_id 300 | |
---|---|---|---|
T1 | 开始事务 | 开始事务 | |
T2 | 更改名字为A | ||
T3 | 更改名字为B | ||
T4 | 提交事务 | 更改名字为C | |
T6 | 提交事务 |
所以此时的版本链如下
咱们每更改一次数据,就会插入一条undo日志,并且记录的roll_pointer指针会指向上一条记录,如图所示
- 第一条数据是小杰,事务ID为100
- 事务ID为200的事务将名称从小杰改为了A
- 事务ID为200的事务将名称从A又改为了B
- 事务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
| 工夫 | 事务100 | 事务200 | 事务300 | 事务400 |
| — | — | — | — | — |
| T1 | A | | | |
| T2 | B | | | |
| T3 | | C | | |
| T4 | | | 读 | |
| T5 | 提交 | | | |
| T6 | | | D | |
| T7 | | | | 读 |
| T8 | | E | | |
| T9 | | 提交 | | |
| T10 | | | 读 | |
## 读已提交(RC)与MVCC
-
一个事务提交之后,它做的变更才会被其余事务看到
每次读的时候,ReadView(一致性视图)都会从新生成
- 当T1时刻时,事务100批改名字为A
- 当T2时刻时,事务100批改名字为B
- 当T3时刻时,事务200批改名字为C
- 当T4时刻时,事务300开始读取名字
- 此时这条数据的版本链如下
同色彩代表是同一事务内的操作
- 来咱们静下心来好好剖析一下此时T4时刻事务300要读了,到底会读到什么数据?
以后最近的一条数据是,C,事务200批改的,还记得咱们前文说的一致性视图的几个属性吗,和依照什么规定判断这个数据能不能被以后事务读。咱们就剖析这个例子。
此时 (生成一致性视图ReadView)
- m_ids 是[100,200]: 以后沉闷的读写事务的事务id列表
- min_trx_id 是 100: m_ids的最小值
- max_trx_id 是 201: m_ids的最大值+1
以后数据的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前提交了,此数据能够被读到。所以读取的数据就是小杰
剖析完第一个读,咱们持续向下剖析
- 当T5时刻时,事务100提交
- 当T6时刻时,事务300将名字改为D
- 当T7时刻时,事务400读取以后数据
- 此时这条数据的版本链如下
此时 (从新生成一致性视图ReadView)
- m_ids 是[200,300]: 以后沉闷的读写事务的事务id列表
- min_trx_id 是 200: m_ids的最小值
- max_trx_id 是 301: m_ids的最大值+1
以后数据事务id是300,数据为D,合乎min_trx_id<=trx_id<max_trx_id 此时须要判断数据是否在沉闷事务列表里,300在这外面,所以就是还未提交的事务就是不可见,所以就去查看上个版本的数据,上个版本事务id是200,数据是C,也在沉闷事务列表外面,也不可见,持续向上个版本找,上个版本事务id是100,数据是B,100小于min_trx_id,就代表,代表生成这个数据的事务曾经在生成ReadView前提交了,此数据可见,所以读取进去的数据就是B
剖析完第二个读,咱们持续向下剖析
- 当T8时刻时,事务200将名字改为E
- 当T9时刻时,事务200提交
- 当T10时刻时,事务300读取以后数据
- 此时这条数据的版本链如下
此时 (从新生成一致性视图ReadView)
- m_ids 是[300]: 以后沉闷的读写事务的事务id列表
- min_trx_id 是 300: m_ids的最小值
- max_trx_id 是 301: m_ids的最大值+1
以后事务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隔离级别下,
- T4时刻时事务300第一次读时的剖析和后果与RC都一样,能够见上文剖析与后果
- T7时刻时事务400第一次读时的剖析和后果与RC都一样,能够见上文剖析与后果
- T10时刻时事务300第二次读时的一致性视图和第一次读时的一样,所以此时到底读取到什么数据就要从新剖析了
此时 (用的是第一次读时生成的一致性视图ReadView)
- m_ids 是[100,200]: 以后沉闷的读写事务的事务id列表
- min_trx_id 是 100: m_ids的最小值
- max_trx_id 是 201: m_ids的最大值+1
此时的版本链是
以后数据的事务id是200,数据是E,在以后事务沉闷列表外面,所以数据不可见,依据回滚指针找到上个版本,发现事务id是300,以后事务也是300,可见,所以读取的数据是D
- 咱们能够本人思考下,要是没有事务300这条更改的这条记录,又该怎么持续向下剖析呢?
当隔离级别是可反复读RR的状况下,每次读都会用第一次读取数据时生成的一致性视图(ReadView)
- T4时刻 事务300读取到的数据是小杰
- T7时刻 事务400读取到的数据是B
- T10时刻 事务300读取到的数据是D
往期精彩举荐
- 京东这道面试题你会吗?
- ?线程池为什么能够复用,我是蒙圈了。。。
- 学会了volatile,你变心了,我看到了
- mysql能够靠索引,而我只能靠打工,加油,打工人!
- 你好,我叫AQS(系列一:加锁)
絮絮叨叨
如果大家感觉这篇文章对本人有一点点帮忙的话,欢送关注此公众号 java小杰要加油
若文章有误欢送指出,靓仔靓女们,咱们下篇文章见,扫一扫,开启咱们的故事