“时光机”与“多维视界”⭐️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/>
2insert 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 公布!