2021年10月22日 星期五 晴
1. 前言
大家好,我是小崔爱读书,今天继续给您讲解《2020年Java面试208题》,本期面试官的问题是:请说一下什么是脏读、不可重复读和幻读?
2. 知识点
咱们来说说这三个问题的现象。
2.1. 脏读、不可重复读、幻读的现象是什么?
脏读:甲去查自己的账户有多少钱,乙正在修改甲的余额,乙错误多打了几个0,然后甲的余额变成了1个亿,甲一看乐疯了。乙也发现了数据错了,还好还好,乙是在事务中修改的,就赶紧回滚了。甲很开心,以为自己实现了人生小目标。乙很放心,知道甲还是一个穷逼。
这个过程中,甲读到了乙事务中修改的数据,乙又做了回滚,甲得到的数据是错误的数据,就是脏读。
不可重复读:甲第二次去查自己有多少钱,他怕再出现之前那种脏读现象,因此就加了事务进行读取数据。甲事务先读了一次余额为1万元,这时候乙又来了,乙修改了甲的余额为 8000元,修改完毕后乙提交并结束了事务。这时候甲还未结束事务,在结束事务前,甲又去查看了一下自己的余额,发现数变了,少了2000块钱。甲傻眼了,加着事务呢,咋重复读了一次余额又不一样了?这就是不可重复读。
幻读:甲郁闷的不行,自己的账面总是错。为了查证账目是否正确,甲决定查查自己的存取款明细。甲查看自己编号 1 - 5之间的出入账明细,看到有 4条,编号分别为 1 2 4 5,没有编号3的账目。这时候,乙悄默声的又来了,乙向甲的账目明细中插入了一条编号为3 的出账记录,并提交了事务。甲这时候又查了一次,发现账目又多了一条出账。甲又郁闷了,难道我产生幻觉了?怎么这次多了一条出账?这就是幻读。
总结一下,脏读就是甲读到了乙未提交事务的数据变更,乙回滚后,数据没有变,但甲以为数据变了。不可重复读就是甲先读了一遍是正确的数据,乙对这些数据进行了修改,甲再读一次是修改后的数据,一个事务中重复多次读到的数据是不同的。幻读就是甲读到几条连续的数据,乙向这几条数据中又插入了一条新的数据,甲再读的时候发现多了一条,以为自己有了幻觉。
这么看起来,脏读、不可重复读、幻读都是多并发情况下出现的数据库问题,都是一个事务修改数据影响了另一个事务读取到数据的正确性。
2.2. 脏读、不可重复读、幻读的区别是什么?
脏读是事务A读到了事务B中未提交的数据。
不可重复读是事务A中读的数据,被事务B修改并提交了,事务A并没有保证自己读到的数据不可修改。
幻读是事务A读某个范围内的数据,这些数据没有被修改,但这些数据中被事务B插入了新数据。
2.3. 事务的隔离机制
这就有点奇怪了,事务不是有隔离性吗?既然已经隔离了,怎么数据还会被其他事务修改呢?
并且,既然三种错误都是一个事务修改数据影响到另一个事务读取数据,为什么还要分的那么细?这不就是读写冲突吗?怎么还细化出来了3种错误?
回答这两个问题,就要详细解释事务的隔离机制,不同级别的隔离机制对并发事务的数据读写错误是如何解决了。由于事务的隔离是分为不同级别的,不同的级别可以解决一部分问题,这就区分出了脏读、不可重复读和幻读的底层解决细节了。
事务隔离级别分为4种:读未提交、读已提交、可重复读、可串行化。
这4种隔离级别分别在性能和高并发下数据冲突之间寻找平衡。性能越好的,高并发下数据越容易冲突,反之亦然。世界不完美,人总是在矛盾中寻找平衡。
读未提交:这种隔离级别是最轻量级别的,对数据不上锁,可以读取到其他事务未提交的事务。
因此,甲就读到了乙未提交的数据,这就是脏读。
读已提交:这种隔离级别下,只能读其他事务已经提交的数据,因此读已提交隔离级别就解决了脏读问题。
但是当事务A先读一次数据,然后事务B修改并提交数据,然后A再读一次数据,就会发现两次读到的数据不一样,因为事务A第一次读的时候和第二次读的时候,都是已提交的数据。因此读已提交隔离级别会造成不可重复读问题。
可重复读:在可重复读隔离级别下,会对读到的数据加锁,即读的数据也不允许其他事务修改。因此可重复读隔离界别就解决了不可重复读的问题。
但可重复读只对已有的数据进行加锁,并不能阻止其他事务插入新数据。如果事务A读取了两条记录,事务B向这两条数据之间又插入了一条记录并提交了事务,然后事务A再读时发现是3条数据了。因此可重复读隔离级别会造成幻读问题。
可串行化:在可串行化隔离级别下,所有的事务是串行执行的,即多个事务是按照先后顺序执行的,这就可以解决了所有的脏读、不可重复读以及幻读问题。因为可串行化其实是将高并发的事务强制阻塞成单线程执行了。这绝对不会出现任何冲突问题,但也可以想象这种隔离级别的性能是超级低的。
由于可串行化的性能太差,一般很少用这种隔离级别的。那么对于幻读这种终极问题怎么解决呢?一般使用间隙锁来处理。
间隙锁:首先我们要知道,其实所有的隔离机制都是通过锁来解决问题的。通过前面的描述我们可以看到可重复读这种隔离方式其实很不错了,能解决很多问题,如果找到一个策略把幻读问题也解决了,那么可重复读隔离方式就完美了。
为了让可重复读隔离不会出现幻读问题,引入了间隙锁这个杀器。
幻读问题的根本是插入数据造成的,如果不允许插入数据自然就解决问题了。间隙锁就是把某个区间(间隙)整体上锁,这个区间的数据不仅不允许修改、删除,也不允许新增。这样就解决了幻读问题。
3. 演示面试
好的,知识点 解释完了,接下来由我来进行演示面试。
您好,面试官。
首先,脏读、不可重复读、幻读,是数据库不同事务隔离级别下产生的问题。
数据库的隔离级别有4种:读未提交,读已提交,可重复读,可串行化。
读未提交隔离级别,事务对数据不加锁,可以读取其他事务未提交的数据。假如事务A采用读未提交隔离级别,事务B正在操作数据但未提交,事务A会读到事务B的未提交数据,事务B回滚事务后,事务A读到的数据就是错误的、脏的。这就是脏读。
读已提交隔离级别,事务只能读取别的事务已经提交的数据,这就从根本上杜绝了脏读的问题,因此读已提交隔离级别就可以解决脏读的问题。但读已提交还存在不可重复读的问题。
例如事务A采用读已提交的隔离级别,它先读了一次数据,然后事务B修改了该数据,事务A又读了一次数据,发现数据变了。这是不应该的,在同一个事务中,未做数据修改操作数据居然变了,这就是不可重复读问题。
可重复读隔离界别解决了不可重复读问题,可重复读隔离级别下锁住数据后,别的事务不能对被锁的数据进行修改,这样就杜绝了不可重复读问题。
但是可重复读隔离级别还存在幻读问题。因为可重复读隔离级别只是锁住了已经存在的数据,但如果另一个事务向数据中间插入一条新数据的话是可以的。比如事务A采用可重复读隔离级别,它以为这些安全了,它查询主键在1-3 之间的数据,发现有两条记录,主键分别为 1 和 3 ,这时候事务B插入了一条主键为2 的记录并提交了事务,然后事务A又查询主键在1-3之间数据,发现有3条记录了。这就是幻读。
最高级别的隔离机制是可串行化,就是排他的,这个事务执行之后,其他事务都得等着,他执行完毕后其他事务才可以执行。由于这种排他性,可以保证一定不会出现幻读问题。但这种隔离机制会造成性能问题,因为不推荐使用。
对于幻读的解决方案,一般采用间隙锁来解决,就是在可重复读隔离级别下,采用间隙锁。幻读出现的原因就是当事务A读取某个范围的数据的时候,事务B向这个范围插入了新的数据,如果不让事务B在这个范围插入数据就可以解决幻读了。间隙锁就是把这个范围整体上锁,这样不仅存在的记录不可以修改,不存在的记录也不允许插入进来,这就解决了幻读问题。
好的,对于脏读、不可重复读、幻读这三个问题的解决方案我就说这么多。
4. 总结
本期文章就写到这里,希望对您能有所帮助,谢谢,我们下期再见。
、