Java面试题~MySQL实战系列之4种事务隔离级别

作者: 修罗debug
版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。



摘要:

对于关系型数据库MySQL,想必各位小伙伴并不陌生,恰逢金九银十、跳槽涨薪之际,Debug特意给诸位小伙伴整理了一些关于MySQL方面的知识点,一来是加深巩固MySQL技术栈,二来可以在跳槽时的面试提供一些助攻;这些知识点将分2-3篇系列文章进行发布,感兴趣的小伙伴可以持续关注哦~

本文我们将从MySQL的几种事务隔离级别开始撸起,从MySQL事务的概念讲起,再到事务的4个基本特性、事务并发时产生的问题,最后再到MySQL的4种事务隔离级别的介绍以及对应的实战。

内容:

一、事务简介与4个基本特性

事务(Transaction)一词是计算机术语,一般指的是要做的或者所做的事情,在计算机编程领域指的是访问并更新数据库中各个数据项的一个执行单元,一般是由两段核心命令之间执行的全体操作组成,即事务开始“begin transaction”、事务结束“end transaction”;而在这两段操作命令之间,常见的就有:提交事务commit 、回滚事务rollback 等等;

而事务有4个基本特性,即我们常说的“酸性”:ACID,每个字母代表一个特性,具体包括:

(1)原子性(Atomicity):事务开始后所有操作,要么全部做完,要么全部不做,不可能停滞在中间环节;事务执行过程中出错,会回滚到事务开始前的状态,所有的操作就像没有发生过一样;也就是说事务是一个不可分割的整体,就像化学中学过的原子,是物质构成的基本单位;

 

(2)一致性(Consistency):事务开始前和结束后,数据库的完整性约束没有被破坏;比如A向B转账,不可能A扣了钱,B却没收到;

 

(3)隔离性(Isolation):同一时间,只允许一个事务请求同一数据,不同的事务之间彼此没有任何干扰;比如A正在从一张银行卡中取钱,在A取钱的过程结束前,B不能向这张卡转账;

 

(4)持久性(Durability):事务完成后,事务对数据库的所有更新将被保存到数据库,不能回滚;

二、事务并发时产生的问题

从上述中我们已经知晓了“事务”其实就是一个或者一组操作单元,如“新增用户信息”、

“更新用户信息同时更新用户头像”等等均为实际项目开发中常见的事务,这一事务涉及到的相关操作要么全部都做完、要么全部都不做;

然而在某些特殊的场景下,比如多线程并发操作同个数据库表 或者 同份数据,则有可能会出现事务的并发问题,这些问题可以归结为3类,即:

(1)脏读:事务A读取了事务B更新的数据,然后B执行回滚操作,此时A读取到的数据即是脏数据;

 

(2)不可重复读:事务 A 多次读取同一份数据,事务 B 在事务A多次读取的过程中,对数据作了更新并提交,导致事务A多次读取同一份数据时,得到的结果不一致;

 

(3)幻读:管理员A将数据库中所有学生的成绩从具体分数改为ABCDE等级,但是管理员B就在这个时候插入了一条具体分数的记录,当系统管理员A修改结束后发现还有一条记录没有改过来,就好像发生了幻觉一样,这就叫幻读。

 

附注:不可重复读的 和 幻读很容易混淆,不可重复读侧重于修改,幻读侧重于新增或删除。解决不可重复读的问题只需锁住满足条件的行,而解决幻读需要锁表

 

三、4种事务隔离级别

既然数据库在使用过程中有可能会出现事务并发的各种问题,那么自然而然我们需要去寻找相应的解决方案;幸运的是,目前各大主流的数据库如MySQL、Oracle、Sql Server等等本身就自带了相应的解决方案,即传说中的“事务隔离级别”;

下面我们以MySQL为例,介绍它的4种事务隔离级别,每一种均用于解决上述事务并发时产生的各种问题,如下表格所示:

事务隔离级别

脏读

不可重复读

幻读

读未提交
(read-uncommitted)

可能出现

可能出现

可能出现

读已提交
(read-committed)

不可能出现

可能出现

可能出现

可重复读
(repeatable-read)

不可能出现

不可能出现

可能出现

串行化

(serializable)

不可能出现

不可能出现

不可能出现

(1)读未提交(read-uncommitted):顾名思义,指的是并发的事务A、B在操作同一份数据时由于先后顺序、事务没提交以及回滚等原因导致最终数据不一致性的现象;

比如变量a初始值为1,即a=1,事务A先读取到变量a,此时Aa=1,在A准备进入后续相关操作之前,B事务对a变量执行了减1操作,但是还没有提交该事务,此时在B看来a仍然为1,即Ba=1(因为还没有提交“减1”操作对应的事务);

而出乎意料的是,此时A事务再次查询变量a的值时会发现a已经变为0了,即Aa=0(这就是所谓的“读已提交”,而理论上应该是1才对,因为B还没提交“减1”操作对应的事务),然后A带着变量a=0进行了后续的操作……

不幸的是,就在A带着变量a=0的值进行后续操作之前,如果此时B进行回滚操作,即回滚“减1”操作,那么此时变量a将变为1,即最终a=1,但是为时已晚,A事务已经拿着变量a=0的值去“潇洒”了~,因此也就会出现“脏读”、“不可重复读”、“幻读”,这一过程可以由下图形象地表示:


(2)对于“读未提交”事务隔离级别,很明显目前许多主流的数据库几乎都不会使用;而如果想解决“读未提交”事务隔离级别所出现的种种问题,我们可以采用“读已提交”的隔离级别进行替代;

即“读已提交”顾名思义指的是事务A最终从数据库中读取到的同份数据势必为事务B“commit提交事务”后的数据,这无疑可以解决“脏读”的问题,即“最终一致性”,但是由于事务A可能会发起多次“查询操作”,每次操作执行的间隔,可能会穿插进B提交事务,从而导致事务A首次查询时变量a=1,而在后续发起相同的查询操作时变量a确为0,即“幻读”和“不可重复读”的问题依旧存在;

 

(3)“可重复读”是MySQL默认的事务隔离级别,可以解决“脏读”、“不可重复读”的问题;

对于“脏读”的问题,在该事务隔离级别下,事务A与事务B分别执行的操作可以说是既独立、又最终相互影响的,所谓的独立,指的是事务不会读到其他事务对已有数据的修改,即使其他事务已经提交,也就是说,事务开始时读到的数据是什么,在事务提交前的任意时刻,这些数据的值都是一样的;而这也顺带解决了“不可重复读”的问题;

但是,对于其他事务如B新插入的数据,事务A是可以读到的,而这也就引发了幻读问题;(当然啦,实际上由于MySQL底层的MVVC机制,这个问题在实操时几乎难以复现)

值得一提的是,“可重复读”这一事务隔离级别之所以可以起到相应的作用,很多原因得得益于它的底层,即底层使用了MVCC机制,即多版本并发控制,即select操作不会更新版本号,是快照读(历史版本);而insert、update和delete会更新版本号,是当前读(当前版本)  ~   找个时间我们再细谈这玩意!

 

(4)而“串行化”这一事务隔离级别可以说是4种事务隔离级别中隔离效果最好的,解决了脏读、可重复读、幻读的问题,但是效果最差,它因为将事务的执行变为顺序执行,与其他三个隔离级别相比,它就相当于单线程的了,后一个事务的执行必须等待前一个事务结束;这家伙在这里就不做过多介绍了!

 

四、简单比较

(1)首先是“读未提交”,它是这4个当中性能最好、但也可以说是最野蛮的方式,因为它“没心没肺”、压根儿就不加锁,所以根本谈不上什么隔离效果,可以理解为没有隔离;

(2)再来说说“串行化”,读的时候加共享锁,也就是其他事务可以并发读,但是不能写,写的时候加排它锁,其他事务不能并发写也不能并发读,即沦落为“单线程”执行了!

(3)最后是“读已提交”和“可重复读”,这两种隔离级别算是比较复杂的,既要允许一定的并发,又想要兼顾解决相应的问题。

 

五、总结

至此,我们已经初步介绍完成了MySQL关于事务以及事务隔离级别方面的知识要点,值得一提的是,MySQL只有InnoDB引擎才支持事务,其中可重复读是默认的隔离级别;

“读未提交”和“串行化”基本上是不需要考虑的隔离级别,因为前者几乎相当于“不加锁”,后者则相当于单线程执行,效率几乎很差;

相对于“读未提交”而言,“读已提交”级别则可以解决脏读的问题,它提供的“行锁”机制可以解决并发更新的问题;

而“可重复读(repeatable-read)”级别则包含了“读已提交”的优点,即同样可以解决“脏读”的问题;而并“幻读”的问题则主要是通过行锁和间隙锁的组合 Next-Key 锁来实现的;


六、彩蛋

如果需要交流技术,可以加下方debug的微信:debug0868