InnoDB在RR隔离级别下到底有没有解决幻读问题?

更多博客:香蕉粗的黄鳝
大家好,我是一根甜苦瓜。
今天就来聊一个面试时经常会被问到的问题(老八股了)

请问InnoDB在RR级别(可重复读)下,完全解决了幻读问题吗?

1. 什么是幻读

简单来说,幻读就是在一个事务内多次执行SELECT COUNT(*) 查询结果不一致。造成的原因大概率是因为其他事务在这个事务执行期间插入/删除了数据,导致查询记录数量不一致。

比如下面的场景:

  1. 事务 A:执行了一次查询,查找所有 age > 18 的用户,得到 10 条记录。
  2. 事务 B:在事务 A 运行期间,插入了一条age = 20的用户记录。
  3. 事务 A:再次查询 age > 18 的用户,此时得到了 11 条记录(事务B插入的数据也被统计进来了)。

在这个过程中,事务 A 的两次查询得到了不一致的结果,第二次多了一条记录,这种现象就是幻读

2. InnoDB在RR级别下解决了幻读问题吗

先不聊结论,先弄点数据测试一下

1. 创建表

使用下面的SQL创建一个表,并插入2条测试数据:

CREATE TABLE `user` (
  `id` int NOT NULL,
  `age` int DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

INSERT INTO `test`.`user` (`id`, `age`) VALUES (1, 11);
INSERT INTO `test`.`user` (`id`, `age`) VALUES (2, 22);

此时user表的情况如下:
在这里插入图片描述
下面就来模拟幻读的场景,不过在开始之前请确保下面的操作完成:

首先所有的事务都是在RR级别下(可以使用SHOW VARIABLES LIKE 'transaction_isolation';来查询当前事务的隔离级别),并且每个事务都执行 set autocommit = 0; 以防止每执行一条SQL语句就自动提交事务。

1. 多个事务同时快照读

分别启动两个事务ABA事务负责查询表中的记录数,B事务负责插入一条记录。他们的执行顺序如下图(从上到下):

在这里插入图片描述

那么上图中两个【?】分别得出的结果是多少?

答案是 22,没有出现幻读场景

由于普通的查询语句 select * from user快照读

快照读就意味着在A事务开启的时候,A事务对表中的数据 拍摄了一个快照,这个快照就像一个照片一样,只记录了A事务开启瞬间整个数据库的情况,后续的其他事务对数据库的修改,对于这张“照片”来说,都是无法被感知的。

所以在B事务插入数据的时候,A事务的快照中并没有感知到这条数据,根据MVCC机制,A事务无论进行多少次 select * from user,都读不到B事务插入的新数据。

下面验证一下:

  1. 阶段一: A事务查询数据,简单的快照读,读出2条数据
    在这里插入图片描述
  2. 阶段二: B事务插入一条数据,没有冲突,提交成功
    在这里插入图片描述
  3. 阶段三:A事务再次查询数据,读取的是 read-view中的数据,所以依然读出2条数据
    在这里插入图片描述
    结论一:在可重复读隔离级别下(RR),普通的查询语句是一种快照读,由于MVCC机制的存在,对于事务执行期间新插入的数据并不可见,所以不会出现幻读问题。

2. 先当前读再快照读

下面换一个思路,将A事务的第一次查询改为当前读,执行顺序如下图(从上到下):
在这里插入图片描述

此时上图中两个**“?”**分别得出的结果是多少?

答案是,第一个"?“是 3,第二个”?"被阻塞

由于A事务的第一次查询 select * from user lock in share mode;是一个当前读lock in share mode意味着加了读锁)。

当前读意味着在A事务查询的时候,对表中的数据加了共享锁,所以在B事务插入数据的时候,B事务被阻塞了无法插入数据,直到A事务提交或者回滚。

  1. 阶段一: A事务查询数据,加了读锁,读出3条数据
    在这里插入图片描述
  2. 阶段二: B事务插入一条数据,被阻塞
    在这里插入图片描述

可以使用下面的命令来查看当前数据库的上锁情况

select * from performance_schema.data_locks;

如下图,其实就是加了 next-key 锁(记录锁+GAP 锁),会锁住所有数据之间的间隙,这会阻止其他事务往锁定范围内插入数据。(由于存在唯一索引,所以记录锁+GAP 锁会退化成 RECORD LOCK。)
在这里插入图片描述
结论二:对于当前读来说,通过 next-key 锁的方式解决了幻读问题。如果有其他事务在 next-key 锁范围内插入了一条记录,那么这个插入语句就会被阻塞,无法成功插入。所以也规避了幻读问题。

3. 先快照读再当前读

再换一个思路,如果A事务的第一次查询是快照读,第二次查询是当前读会发生什么?执行顺序如下图(从上到下):
在这里插入图片描述

此时上图中两个**“?”**分别得出的结果是多少?

答案是,第一个"?“是 3,第二个”?"是 4

  1. 阶段一: A事务查询数据,简单的快照读,读出3条数据
    在这里插入图片描述

  2. 阶段二: B事务插入一条数据,没有冲突,提交成功
    在这里插入图片描述

  3. 阶段三:A事务再次查询数据,这次是当前读,读出4条数据
    在这里插入图片描述

3. 总结

在很大程度上,InnoDBRR 隔离级别下分别用 MVCCNext-Key Locks 解决了幻读问题。但是在特殊情况下,快照读和当前读同时存在的情况下仍未解决幻读问题。

Next-Key Locks 是记录锁(Record Locks)和间隙锁(Gap Locks)的组合,用于锁定索引记录及其之间的间隙。在范围查询时,Next-Key Locks 可以防止其他事务在这些间隙中插入新记录,从而解决了幻读问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一根甜苦瓜

祝老板身体棒棒,夜夜笙歌

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值