关于后端:时光机与多维视界⭐️MySQL中原子性与隔离性的科幻大片

38次阅读

共计 3289 个字符,预计需要花费 9 分钟才能阅读完成。

“时光机”与“多维视界”⭐️MySQL 中原子性与隔离性的科幻大片

上篇文章 咱们形容完 MySQL 的持久性等知识点,本篇文章来形容 MySQL 的原子性与隔离性知识

”时光机“指的是实现原子性的 undo log,”多维视界“指的是实现并发场景下读不加锁的 MVCC,一起往下看看吧~

内容脑图如下:

MySQL 中反对事务的只有 Innodb,因而本篇文章形容的原理也是 Innodb 实现原子性的原理

事务的原子性:一组事务要么都胜利,要么都失败

在事务中可能存在一些写操作(新增 / 批改 / 删除),有的写操作会执行胜利,然而后续写操作执行失败就会导致事务须要回滚,那么执行胜利的写操作就要退回到原来的“版本”

为了不便回滚,Innodb 应用 undo log 来记录每次写操作批改的内容 ,依据 undo log 可能疾速退回到原始版本

每一行记录中存在暗藏列 roll\_pointer 回滚指针,它指向上一次写操作产生的 undo log,undo log 中也存在相似回滚指针的列,它可能找到它上一次写操作产生的 undo log

记录与 undo log 就会造成一条”版本链“,这样在遇到回滚时便能疾速的回到原来的版本以此来实现原子性(回滚)

留神:undo log 只是记录批改的数据,并不是残缺数据,图中只是为了不便展现,图中执行 SQL 程序为从下到上

隔离性

为了避免并发事务穿插执行导致的数据不统一等并发问题,MySQL 会依据不同的隔离级别来解决不同的隔离性问题

隔离性问题与隔离级别

不同隔离级别下会应用不同的计划来达到隔离性,咱们先来温习一下隔离性问题和隔离级别:

隔离性问题

脏写:A 事务和 B 事务同时批改一行记录,A 能够笼罩 B 批改后未提交的记录,后续 B 又进行回滚

脏读:A 事务读取 B 事务未提交的记录,而后 B 事务回滚,导致 A 事务读取的数据不统一

不可反复读:A 事务前后两次执行同一个查问,返回的后果数据不同,B 事务在此期间进行批改

幻读:A 事务前后两次执行同一个查问,返回的后果数量不同,B 事务在此期间进行新增

幻读强调后果数量不同,不可反复读强调后果数据不同

隔离级别

读未提交(RU):能够读到其余事务未提交的记录,会呈现脏读、不可反复读、幻读

读已提交(RC):只能读到其余事务提交的记录,会呈现不可反复读、幻读

可反复读(RR):Innodb 默认,不会呈现不可重读的状况,可能呈现局部幻读

串行化(S):所有隔离性问题都会不产生

随着隔离性的严格,性能也会升高:RU > RC > RR > S

MVCC

那么在 undo log 的版本链中是如何做到有的事务可能看到该版本、有的事务看不到该版本的呢?

其实这是通过 MVCC(Multi Version Concurrency Control 多版本并发管制)来实现的,MVCC 在这种读写并发的场景下,应用无锁机制读取到满足隔离级别的数据

对于事务来说,当事务中呈现第一条写操作的语句时就会生成事务 ID,这个事务 ID 是全局递增的

当应用 MVCC 时会生成 read view 来判断以后事务可能读到哪个版本上的记录

read view 中就蕴含事务 ID 相干的属性,便于判断事务能读到哪个版本

  1. 最小事务 ID:read view 生成时沉闷事务中最小的 id
  2. 最大事务 ID:read view 生成时沉闷事务中最大的 id
  3. 沉闷事务列表:read view 生成时存在的沉闷事务

后面说到:事务 ID 是全局自增的,这示意能够依据事务 ID 查看哪个事务先执行、哪个事务后执行、哪个事务已提交 / 回滚、哪个事务以后还在执行中

应用 read view 与版本链上的最新记录进行判断(判断规定如下):

  1. 如果 read view 的最小事务 id 大于该记录的事务 id,阐明该记录的事务曾经提交了,那么是能够查看的
  2. 如果 read view 的最大事务 id 小于该记录的事务 id,阐明该记录的事务在以后事务开启后才开始,那么无奈查看
  3. 如果该记录 id 在 read view 最小、大事务 id 之间,那么要查看以后沉闷事务列表,如果在列表中,阐明该列的事务沉闷(还未完结),则无奈查看;反之则能够查看
  4. 如果记录的事务 id 是以后事务,则能够查问 (以后事务执行的,当然能够查看)

当该版本的记录无奈查看时,就会遍历版本链持续向下一条记录应用该规定

比方 read view 最小事务 id:20 最大事务 id:30 沉闷事务列表:20,22,24,27,30

当记录的事务 id 为 19 时,小于最小事务 id,阐明它曾经提交能够查看

当记录的事务 id 为 31 时,阐明生成 read view 时,它还没开启事务,不能查看

当记录的事务 id 为 24 时,在最小 19、最大 30 之间,要查看沉闷事务列表中是否存在,存在则不能查看

当记录的事务 id 为 23 时,沉闷事务列表中不存在则能够查看

剖析读相干解决隔离性问题的计划

在理解隔离性问题与隔离级别后,咱们来进行一一剖析:

脏写产生在写写的场景下会毁坏数据的一致性,禁止这种状况产生,会应用行锁保障(锁相干常识下篇文章再说)

脏读产生在读写的场景下会读的数据不统一,也要禁止这种状况产生,失常状况读写场景下须要保证数据一致性的方法就是加行锁相互阻塞

RC 应用 MVCC 机制,在进行读操作时应用 read view 查看版本链上的记录是否可读 ,如果事务已提交(事务 id 会比 read view 最小事务 id 要小),以此通过无锁机制达到避免脏读的成果

不可反复读偏重的是有其余事务在本次事务屡次查问过程中批改这个数据的内容(幻读偏重数量)

RC 下,因为每次读都生成 read view,前后两次读生成的 read view 不同,能看到的数据也就可能不同

RR 应用 MVCC 机制,在同个事务中只有第一次读生成 read view,后续读都应用这个 read view,以此来达到避免不可反复读,和大部分幻读

幻读案例

为啥说 RR 能够避免大部分幻读呢?

RR 通过 read view 能够看到的数据是不变的,因而能够避免幻读

那为啥说是避免大部分幻读而不是所有幻读呢?

还记得第四条规定(如果记录的事务 id 是以后事务,则能够查问)吗?

在某些场景下就会导致非凡幻读,来看一个案例:

工夫点 T1T2
1begin;<br/>SELECT * FROM s where id < 30;<br/>
2 insert into s (id,s\_name,s\_age) value (18,’caicai’,18);
3update s set s\_name = ‘ 菜菜的后端私房菜 ’ where id = 18;<br/>
4SELECT * FROM s where id < 30;<br/>commit;

T1 先进行查问(-∞,30),T2 在该区间插入一条记录 (18),如果此时再进行读则数据还是统一的

然而 T1 对这条新增的记录进行批改,导致 T1 的 read view 对该事务可见,从而导致第二次读到这条 T2 新增的数据产生幻读

在 S 串行化下,如果是主动提交则只有一条读会应用 mvcc,写则会加锁

至此,咱们形容完读相干解决不同隔离性的计划,写相干解决不同隔离性的计划与锁相干,我把它放在下一篇文章和锁一起进行解说

总结

记录的暗藏列中存在回滚指针和事务 ID,回滚指针指向 undo log,undo log 又能够通过指针找到上一次批改产生的 undo log,从而造成版本链(undo log 只记录批改数据)

事务的原子性(须要回滚时)能够通过 undo log 实现

在并发读写场景下,Innodb 的读操作通过 mvcc 来保障不同隔离性下数据一致性

mvcc 应用生成 read view、全局递增的事务 ID 和可能看到哪个版本的校验规定来实现

RU 读时没应用 mvcc,能够间接读到版本最新记录

RC 每次读时生成 read view,只能避免脏读

RR 第一次读生成 read view,能够避免脏读、不可反复读、大部分幻读

S 只在主动提交时应用 mvcc

最初(不要白嫖,一键三连求求拉~)

本篇文章被支出专栏 MySQL 进阶之路,感兴趣的同学能够继续关注喔

本篇文章笔记以及案例被支出 gitee-StudyJava、github-StudyJava 感兴趣的同学能够 stat 下继续关注喔~

有什么问题能够在评论区交换,如果感觉菜菜写的不错,能够点赞、关注、珍藏反对一下~

关注菜菜,分享更多干货,公众号:菜菜的后端私房菜

本文由博客一文多发平台 OpenWrite 公布!

正文完
 0