18、RR解决了幻读?
...About 4 min
18、RR解决了幻读?
Repeatable Read 解决了幻读问题吗
可重复读(repeatable read)定义: 一个事务执行过程中看到的数据,总是跟这个事务在启动时看到的数据是一致的。
不过理论上会出现幻读,简单的说幻读指的的当用户读取某一范围的数据行时,另一个事务又在该范围插入了新行,当用户在读取该范围的数据时会发现有新的幻影行。
注意在可重复读隔离级别下,普通的查询是快照读,是不会看到别的事务插入的数据的。因此, 幻读在“当前读”下才会出现(查询语句添加 for update,表示当前读);
在 MVCC 并发控制中,读操作可以分为两类: 快照读(Snapshot Read)与当前读 (Current Read)。
- 快照读
快照读是指读取数据时不是读取最新版本的数据,而是基于历史版本读取的一个快照信息(mysql 读取 undo log 历史版本) ,快照读可以使普通的 SELECT 读取数据时不用对表数据进行加锁,从而解决了因为对数据库表的加锁而导致的两个如下问题- 解决了因加锁导致的修改数据时无法对数据读取问题.
- 解决了因加锁导致读取数据时无法对数据进行修改的问题.
- 当前读
当前读是读取的数据库最新的数据,当前读和快照读不同,因为要读取最新的数据而且要保证事务的隔离性,所以当前读是需要对数据进行加锁的(插入/更新/删除操作,属于当前读,需要加锁,select for update为当前读)
表结构
| id | key | value |
|---|---|---|
| 0 | 0 | 0 |
| 1 | 1 | 1 |
假设 select * from where value=1 for update,只在这一行加锁(注意这只是假设),其它行不加锁,那么就会出现如下场景:

Session A 的三次查询 Q1-Q3 都是 select * from where value=1 for update,查询的 value=1 的所有 row。
- T1:Q1 只返回一行(1,1,1);
- T2:session B 更新 id=0 的 value 为 1,此时表 t 中 value=1 的数据有两行
- T3:Q2 返回两行(0,0,1),(1,1,1)
- T4:session C 插入一行(6,6,1),此时表 t 中 value=1 的数据有三行
- T5:Q3 返回三行(0,0,1),(1,1,1),(6,6,1)
- T6:session A 事物 commit。
其中 Q3 读到 value=1 这一样的现象,就称之为幻读,幻读指的是一个事务在前后两次查询同一个范围的时候,后一次查询看到了前一次查询没有看到的行。
先对“幻读”做出如下解释:
- 要讨论「可重复读」隔离级别的幻读现象,是要建立在「当前读」的情况下,而不是快照读,因为在可重复读隔离级别下,普通的查询是快照读,是不会看到别的事务插入的数据的。
Next-key Lock 锁
产生幻读的原因是,行锁只能锁住行,但是新插入记录这个动作,要更新的是记录之间的“间隙”。因此,Innodb 引擎为了解决「可重复读」隔离级别使用「当前读」而造成的幻读问题,就引出了 next-key 锁,就是记录锁和间隙锁的组合。
- RecordLock 锁:锁定单个行记录的锁。(记录锁,RC、RR 隔离级别都支持)
- GapLock 锁:间隙锁,锁定索引记录间隙(不包括记录本身),确保索引记录的间隙不变。(范围锁,RR 隔离级别支持)
- Next-key Lock 锁:记录锁和间隙锁组合,同时锁住数据,并且锁住数据前后范围。(记录锁+范围锁,RR 隔离级别支持)

总结
- RR 隔离级别下间隙锁才有效,RC 隔离级别下没有间隙锁;
- RR 隔离级别下为了解决“幻读”问题:“快照读”依靠 MVCC 控制,“当前读”通过间隙锁解决;
- 间隙锁和行锁合称 next-key lock,每个 next-key lock 是前开后闭区间;
- 间隙锁的引入,可能会导致同样语句锁住更大的范围,影响并发度。
Powered by Waline v2.15.8