乐趣区

关于mysql:我身边的高T问了Java面试者这样的问题

大家好,我是来自 JDL 京东物流技术发展部邢焕杰,明天来分享一个京东面试真题,这也是我前阵子听我工位旁边高 T(高,实在是高)面试候选人的时候问的一个问题,他问,你能说说 MySQL 的事务吗?MVCC 有理解吗?话不多说,本文就深度解析一下 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,别离来批改此数据

所以此时的版本链如下

咱们每更改一次数据,就会插入一条 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 开始读取名字
  • 此时这条数据的版本链如下

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

  • 来咱们静下心来好好剖析一下此时 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 前提交了,此数据能够被读到。所以读取的数据就是 小杰。

剖析完第一个读,咱们持续向下剖析

  1. 当 T5 时刻时,事务 100 提交
  2. 当 T6 时刻时,事务 300 将名字改为 D
  3. 当 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。

剖析完第二个读,咱们持续向下剖析

  1. 当 T8 时刻时,事务 200 将名字改为 E
  2. 当 T9 时刻时,事务 200 提交
  3. 当 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隔离级别下,

  1. T4 时刻时事务 300 第一次读时的剖析和后果与 RC 都一样,能够见上文剖析与后果
  2. T7 时刻时事务 400 第一次读时的剖析和后果与 RC 都一样,能够见上文剖析与后果
  3. 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

欢送点击【京东科技】,理解开发者社区

更多精彩技术实际与独家干货解析

欢送关注【京东科技开发者】公众号

退出移动版