《张三求职日记》基础篇--(6)MySQL数据库隔离级别

219 阅读11分钟
写在前面:作为程序员,数据库当然是大家必须都了解的技术了。而如果是Java程序员,特别是互联网的Java开发程序员,数据库肯定是使用频率相当高的技术了,但是依然有很多人没有深入了解过数据库,仅仅只会写点SQL,但SQL在面试中仅仅在笔试中有那么几类题会出现,而数据库相关的知识却是能问出很多花样来。只会写CRUD的程序员在接触过Mybatis Generator等插件后会不会有危机感呢?那还不赶快多补充些深入点的知识呀。有关于张三的背景介绍参考:张三背景介绍

张三又开始了一轮面试。

面试官:先来个自我介绍吧。

张三:!@#¥%……&*。

前面的一阵交谈此处省略。

面试官:看你简历里数据库用到了MySQL和Oracle,平时SQL写的多吗?

张三:当然,我们对数据库的使用还是很频繁的,老项目MySQL和Oracle都有,新项目都是MySQL了。

面试官:你们MySQL数据库用的存储引擎是什么呀。

张三心想,这面试官靠谱,一看就也是干活的,还好为以前也建过不少表下面的应答应该问题不大。

张三:我们用的是InnoDB作为存储引擎。

面试官:那你觉得存储引擎是表级别的还是数据库级别的呢?

张三:那当然是表级别的啊,建表都要选存储引擎,编码格式等等东西呢。

面试官:很好,InnoDB底层数据解构是什么有了解吗?

张三心想,这不是网上那些培训机构讲烂了的东西吗,小意思。

张三:底层是B+树呀。

面试官:那么使用B+树有什么好处呢?

张三:与普通的二叉树相比,B+树的一个叶子节点能存储更多的数据,相同的数据量下使用B+树的树高会更低一点,因为数据是存储在磁盘上面的,更低的树高意味着更少的查询次数,会有更高的效率。与B-树(注意这里读B树)相比呢,因为B-树的数据是放在每一个节点上面的,而B+树的数据是放在叶子节点上面的,并且是连续的,这样使用B+树做遍历或者是范围查询时性能会更加优秀。

面试官:那数据库的事务隔离级别有了解吗?

张三心想,怎么不继续问B+树呢,我还能和你吹好一会呢,数据库隔离级别我也是复习过的,肯定没问题。

张三:数据库的事务隔离级别呀,我想想,分为四个级别,分别是读未提交,读已提交,可重复度以及串行化。

面试官:那这些隔离级别分别为了解决什么问题呢?

张三:读已提交是为了解决脏读,可重复度是为了解决不可重复度的问题,串行化是为了解决幻读,要详细介绍一下脏读幻读不可重复读吗?

面试官:那你说说吧。

张三:脏读啊,就是我查询数据时读到了别人修改过但是没有提交的数据。而不可重复读比如说是我读了两次,第一次读到了一个数据,第二次又读了一遍这个数据但是读的结果不一样,被别人修改了,导致出现问题。幻读嘛……(我为什么要给自己挖坑,回想一下肯定能记起来的),幻读就是我第一次读了一批数据,而第二次再来读的时候,发现多了一个数据,就像是产生幻觉了……

面试官:那MySQL的默认的事务隔离级别是什么呢?

张三心想,果然越问越深啊,我有印象好像是第二个或者第三个,MySQL和Oracle还不一样,既然都说Oracle更安全,怕是MySQL的是第二个吧。

张三:我记得好像是读已提交(注意这里答错了)。

面试官感觉已经问的差不多了,后面再问为什么默认可重复的读估计也打答不出来了。

面试官:那我们再说说……

数据库相关的暂时问完了,后面的省略。

面试官:这次的面试就先到这里,后面会有我们的HR联系你。

张三:好的……

---------------------------------------------------------------------------

所以数据库是真的能问很多东西啊,大家一定要好好准备,因为张三在数据库的隔离级别上栽了跟头,那我们就好好聊一聊数据库的隔离级别。

同样的,我们先分析是不是。那么数据库(MySQL)的隔离级别有几种,怎么验证呢?

首先,我们在MySQL的连接工具的查询框中输入以下查询语句查询当前事务的隔离级别:

SELECT @@global.tx_isolation

得到的结果:

                                           


我们看到查询到的结果为REPEATABLE-READ,各位英语应该都不差吧,这个单词的字面意思是可重复读,也就是说MySQL(版本5.7,存储引擎InnoDB)的默认隔离级别为可重复读。好了,这个知识点我们轻松搞定了,大家应该也能记得住吧。

那么之前张三说隔离级别有四种,我们怎么看呢?抱歉,我也没找到怎么看出有四种的,但是我们可以把大家都说的隔离级别都设置一下看看会不会报错呀,四种隔离级别分别为READ-UNCOMMITTED | READ-COMMITTED | REPEATABLE-READ | SERIALIZABLE,对应的中文翻译为读-未提交,读-已提交,可重复读,可串行化。那么设置隔离级别的语句是什么呢?

set global transaction isolation level read committed

通过类似这样的语句就能设置隔离级别了,不试一试怎么知道究竟有哪些隔离级别呢?

既然我们知道了有四种隔离级别,那么我们就来介绍一下它们吧,虽然早就被别人讲烂了,但是我还是需要好好介绍一下的。

READ-UNCOMMITTED:读未提交,顾名思义,就是我的事务能够读取到其他未提交事务的执行结果,这样会出现什么问题呢?那就是读的内容不对呀,明明别人都改了某个数据你却读没改之前的,就会出现所谓的脏读。也是因为这个问题,基本不太可能有人会选用这个隔离级别的。

READ-COMMITTED:读已提交,为了解决上面出现的脏读问题,就有对应的读已提交隔离级别?那是怎么解决的呢?前面说你能读到其他未提交的事务的执行结果,那到这一隔离级别就把这一点封死不就行了吗,我不允许你读到别人修改了但是没有提交的结果。如何不允许呢?等呗,等别人提交完了你再读。这个隔离级别是Oracle和SQLServer的默认隔离级别。这样能解决幻读问题,也好像这样做已经能解决绝大部分问题了,但是前面也提到了一个问题叫做不可重复读,这是什么呢?那就是我在一个事务中第一次读了某条记录后,其他的事务改了这一条数据,而我又读了一次,那么我是读没改之前的还是读改了之后的呢?很遗憾,在这个事务下并不能保证别人不会影响我们的读取,也就是我们可能会读到两次不一样的值,在某些情况下就有可能出现问题。

REPEATABLE-READ:可重复读,既然你会出问题,我就有对应的解决方案呗,你的问题叫不可重复读,我的隔离级别就要可重复读。可重复读隔离级别做了什么呢?那就是在我的事务启动并且没有结束的时候,其他事务不允许修改我影响的值了,这样就解决了两次读的不一样的问题了。 注意了,可重复读是MySQL的默认隔离级别。那么解决了不可重复读的问题,就差最后一个幻读的问题了。前面说到了,可重复度的隔离级别是不允许你修改值,但我想增加一个值你管的着吗?你读了一批数据,第二遍读的比第一遍多一个,你就懵逼了,这就是幻读。

SERIALIZABLE:串行化,那么幻读有没有解决的办法呢?的确,按照正常的思维来说,别人的增加或者删除本来与我们查询是没有直接关系的,我们出现幻读的情况也算合理。要是想解决幻读的问题,那么就要使用串行化的隔离级别,原理就是让事务串行执行,也就是事务排好队一个一个来,这样别人的事务就只能等到我的事务执行完后再执行了,也就影响不到我了。但是这要执行的事务必然会有性能差的问题,所以也很少有人会设置这样的隔离级别。
 

                                    


那么四种隔离级别介绍完了,对应可能会出现的问题也顺便介绍完了,查询隔离级别和修改隔离级别的语句也给到了,大家有空的话一定要自己验证一下。

那么最后再问一个问题,MySQL的默认事务隔离级别为什么是可重复读呢?

那么根据我们之前学到的知识,数据库的事务隔离级别是为了解决问题的,明显是越后面的隔离级别能力越强,就看数据库的开发者觉得默认应该需要什么样的隔离级别了。那我们站在数据库的开发者的角度思考一下呢,首先,读未提交什么也没做,连别人的事务做的修改我都有可能读不到,问题很大,很容易出现问题,我们不可能选这个级别作为默认的隔离级别。而串行化效率很低,并且为了解决的*幻读*的问题在很多情况下其实也算不上是问题,选用这种隔离级别很有可能得不偿失。那我们就会在读已提交和可重复读中选取,前面说到了,可重复读的隔离级别是为了解决不可重复读的问题,而不可重复读的情况一个是少,一个是对于某些业务来说,也只能说是业务上的逻辑问题而不是设计上的问题,所以本质上要不要解决可重复读的问题其实不一定是必须的,在我看来这两种都是可取的,只是看更高的要求与更高的性能如何取舍了。

但是其他数据库选读已提交作为默认隔离级别,为什么偏偏你MySQL就要选可重复读呢?总不能是你就是有个性吧?

再去深究一下,其实到这个层次已经很难自己验证对与不对了,只能结合别人的说法与自己的思考来回答问题了:

  1. MySQL的默认事务隔离级别为可重复读与历史原因主从复制有关。
  2. MySQL的主从复制是基于binlog的--有关于binlog再简单讲一讲吧:binlog是MySQL的数据库二进制日志,用于记录用户对数据库操作的SQL语句信息,也就是数据库做了哪些改变都记录在binlog中。
  3. 而在MySQL5.0之前binlog只支持STATEMENT模式,这种模式在读已提交上可能出现问题--binlog中有三个模式:STATEMENT模式也就是每一条会修改数据库的sql会记录到binlog中。ROW不记录每一条SQL语句的上下文,只记录哪条数据被修改了,修改成什么样子。MIXED上面两种混合使用,一般的复制使用STATEMENT模式保存binlog,对于STATEMENT模式无法复制的操作使用ROW模式保存binlog。
  4. 为了解决主从复制可能会出现的数据不一致的问题,MySQL选取了可重复读为隔离级别,并一直延续到了现在--至于为什么会出现问题,为什么设置后解决了由于篇幅问题,只能各位自己去探究了。

好了,这一波下来张三对数据库的认知又上升了一分,张三的面试就到这里,我们下周再见。