一、数据库四种隔离级别

  1. RU(READ-UNCOMMITTED)
    读取事务未提交的数据。
  2. RC(READ-COMMITTED)
    读取到事务已提交的数据。
  3. RR(REPEATABLE-READ)
    可重复读
  4. SR(SERIALIZABLE)
    串行化

二、四种隔离级别与脏读、幻读、不可重复读


要想了解什么是脏读、幻读、不可重复读,可前往《看图说话:对脏读、不可重复度、幻读进行总结》。

三、MVCC

作为下边内容的基础,我们有必要先了解下什么是 MVCC。

MVCC(Multiversion Concurrency Control),即多版本并发控制。

在 MVCC 中,有两种读的概念:「快照读」「当前读」,如下:

  1. 快照读
    所谓 「快照读」,就是将此刻数据库的状态或者查询结果作为一个快照记录下来,像我们通常的查询(不加锁的查询)就属于快照读,例如:select * from t_user where …

    对于 「快照读」,我们需要注意的是 TA 在 RC 和 RR 中的表现是不一样的:

    • 在 RC 中,每次读取都会生成一个基于数据库最新状态的一个快照。
    • 在 RR 中,快照只会在事务中第一次查询的时候生成,只有在当前事务中发生了数据修改才会触发更新快照。
  2. 当前读
    所谓 「当前读」,就是读取当前数据库的最新数据,例如:加锁的 select for updatde,update,insert,delete 都属于当前读。

    这里要提下应该如何理解 update,delete 当前读(因为他们都不是读操作啊为啥叫当前读),要知道 update 和 delete 是要先找到对应的数据才能进行操作的(所以这里实际上也有读),举个例子,在 RR 下,事务A中的 update 可以更新不存在该事务中的数据,即可以在事务A中对事务B中 insert 的一条数据进行 update 操作。

四、RR 是如何解决幻读问题的?

这里要从两个方向来进行说明,如下:

  1. 普通查询(select)
    基于 MVCC,我们知道普通查询是快照读,回顾下上边提到的知识点:在 RR 中,快照只会在事务中第一次查询的时候生成,只有在当前事务中发生了数据修改才会触发更新快照,看下图:

    总结:「普通查询」是通过每次读取相同的快照来解决幻读的问题。

  2. 加锁查询(select for update)
    「普通查询」是通过每次读取相同快照来解决了幻读的问题,那加锁的查询呢?TA 不读快照,读取的最新的数据?这种情况下如何解决幻读呢?

    这里提出了一个概念 「间隙锁」,也就是在 select for update 的时候,不但会对查出的记录加锁,同时会对记录之间的间隙加锁,这就是所说的 「间隙锁」

    由于记录之间也被加了锁,导致其他事务的操作(INSERT or DELETE)将会被阻塞,一直到上一个事务释掉锁,其他事务才能执行。也正是因为其他事务的操作(INSERT or DELETE)无法执行,也就无法使查询结果产生变化(变多或者变少),这样也就避免了出现幻读的情况。

    总结:在 RR 级别中发生「当前读」的情况(即select for update),是通过「间隙锁」来防止幻读的。

综上所诉,RR 是通过「MVCC」和「间隙锁」来解决幻读问题的。

五、为什么我说“RR只是有限的解决了幻读的问题”

上边说的就是在 RR 级别下产生幻读全部场景了吗?为什么我会在开头的时候说 RR 只是有限的解决了幻读问题?下面我们再来看两个场景,如下图:

  1. 场景一

    由于第三次查询是当前读,并没有读取快照,所以造成第三次和前两次查询的结果不一致。

  2. 场景二

    由于当前事务中发生了数据修改,触发了快照更新,所以造成的了第三次查询与前两次查询的结果不一致。

六、SpringBoot 中的事务隔离级别

  1. DEFAULT:默认值,DEFAULT 是 SpringBoot 的默认隔离级别,表示使用底层数据库的默认隔离级别。
    大部分数据库为 READ COMMITTED,MySql 默认隔离级别为 REPEATABLE READ
  2. READ UNCOMMITTED:读未提交
  3. READ COMMITTED:读已提交
  4. REPEATABLE READ:可重复读
  5. SERIALIZBLE:串行化