一、数据库四种隔离级别
- RU(READ-UNCOMMITTED)
读取事务未提交的数据。 - RC(READ-COMMITTED)
读取到事务已提交的数据。 - RR(REPEATABLE-READ)
可重复读 - SR(SERIALIZABLE)
串行化
二、四种隔离级别与脏读、幻读、不可重复读
要想了解什么是脏读、幻读、不可重复读,可前往《看图说话:对脏读、不可重复度、幻读进行总结》。
三、MVCC
作为下边内容的基础,我们有必要先了解下什么是 MVCC。
MVCC(Multiversion Concurrency Control),即多版本并发控制。
在 MVCC 中,有两种读的概念:「快照读」和「当前读」,如下:
-
快照读
所谓 「快照读」,就是将此刻数据库的状态或者查询结果作为一个快照记录下来,像我们通常的查询(不加锁的查询)就属于快照读,例如:select * from t_user where …对于 「快照读」,我们需要注意的是 TA 在 RC 和 RR 中的表现是不一样的:
- 在 RC 中,每次读取都会生成一个基于数据库最新状态的一个快照。
- 在 RR 中,快照只会在事务中第一次查询的时候生成,只有在当前事务中发生了数据修改才会触发更新快照。
-
当前读
所谓 「当前读」,就是读取当前数据库的最新数据,例如:加锁的 select for updatde,update,insert,delete 都属于当前读。这里要提下应该如何理解 update,delete 当前读(因为他们都不是读操作啊为啥叫当前读),要知道 update 和 delete 是要先找到对应的数据才能进行操作的(所以这里实际上也有读),举个例子,在 RR 下,事务A中的 update 可以更新不存在该事务中的数据,即可以在事务A中对事务B中 insert 的一条数据进行 update 操作。
四、RR 是如何解决幻读问题的?
这里要从两个方向来进行说明,如下:
-
普通查询(select)
基于 MVCC,我们知道普通查询是快照读,回顾下上边提到的知识点:在 RR 中,快照只会在事务中第一次查询的时候生成,只有在当前事务中发生了数据修改才会触发更新快照,看下图:
总结:「普通查询」是通过每次读取相同的快照来解决幻读的问题。 -
加锁查询(select for update)
「普通查询」是通过每次读取相同快照来解决了幻读的问题,那加锁的查询呢?TA 不读快照,读取的最新的数据?这种情况下如何解决幻读呢?这里提出了一个概念 「间隙锁」,也就是在 select for update 的时候,不但会对查出的记录加锁,同时会对记录之间的间隙加锁,这就是所说的 「间隙锁」。
由于记录之间也被加了锁,导致其他事务的操作(INSERT or DELETE)将会被阻塞,一直到上一个事务释掉锁,其他事务才能执行。也正是因为其他事务的操作(INSERT or DELETE)无法执行,也就无法使查询结果产生变化(变多或者变少),这样也就避免了出现幻读的情况。
总结:在 RR 级别中发生「当前读」的情况(即select for update),是通过「间隙锁」来防止幻读的。
综上所诉,RR 是通过「MVCC」和「间隙锁」来解决幻读问题的。
五、为什么我说“RR只是有限的解决了幻读的问题”
上边说的就是在 RR 级别下产生幻读全部场景了吗?为什么我会在开头的时候说 RR 只是有限的解决了幻读问题?下面我们再来看两个场景,如下图:
-
场景一
由于第三次查询是当前读,并没有读取快照,所以造成第三次和前两次查询的结果不一致。 -
场景二
由于当前事务中发生了数据修改,触发了快照更新,所以造成的了第三次查询与前两次查询的结果不一致。
六、SpringBoot 中的事务隔离级别
- DEFAULT:默认值,DEFAULT 是 SpringBoot 的默认隔离级别,表示使用底层数据库的默认隔离级别。
大部分数据库为 READ COMMITTED,MySql 默认隔离级别为 REPEATABLE READ - READ UNCOMMITTED:读未提交
- READ COMMITTED:读已提交
- REPEATABLE READ:可重复读
- SERIALIZBLE:串行化