序
摸鱼的时候看到某技术群里有一个问题和下面的回复,在讨论幻读与MySQL的快照读
问:大佬们为什么会产生幻读,是不是因为重新生成了readview?
首先明确一个定义,幻读是指多次查询时,查询到的数据数量出现了变化。
例如:第一次查询到了10行,第二次查询到了11行。
这种情况在MySQL的Read Committed(RC)隔离级别中会出现,在标准数据库定义中,Repeatable Read(RR)隔离级别中也会出现,MySQL通过快照读的方式避免了幻读。
和问题中所说的是不是因为readview导致的幻读,其实恰恰相反readview是MySQL实现快照读所生成的数据结构,但并不是说快照读就一定不会出现幻读,下面再细讲
A群友答:幻读只有在当前读才会出现,当前读读的是最新值,不是读的视图。
B群友答:读已提交的隔离级别下快照读也会造成现幻读
C群友答:要搞清楚幻读的概念, 仅在可重复读隔离级别的当前读
D群友答:RC RR都会出现幻读,需要靠间隙锁解决,光靠mvcc是解决不了幻读问题
其实我回复的是群友D的答案,其他只是在扣概念可以忽略,仅作为上下文阅读,但D的回答是明显有误的
详细说明
前面也提到MySQL是通过快照读避免幻读的,MySQL通过undo log,记录每次修改的回滚操作,可以理解成一条链表。
例如:
- 原值A=1
- 事务1,set A=2
- 事务2,set A = 3
- 事务3,set A= 4
那么链表上就有4个节点,1->2->3->4,并且节点上也会记录做这次修改的事务id,通过事务id和和undo log,我们就可以推算出可见的数据版本了。
可见性规则
在事务开始的时候,会记录当前未提交的所有事务id,这个就是提问中所说的readview。
判断逻辑
从最新的版本开始判断,逻辑如下:
- 当前版本如果比记录的所有事务id都大,即在当前事务开始的时候,该事务并未启动,所以一定不可见的。
- 当前版本如果比记录的所有事务id都小,即在当前事务开始的时候,该事务已提交,所以可见
- 当前版本在readview中,即在当前事务开始的时候,该事务未提交,不可见
- 当前版本不在readview中,即在当前事务考试的时候,该事务已提交,可见
- 更新逻辑,因为更新不可能在快照上做更新,所以更新的时候是读取最新的数据上再做修改,且因为修改之后的undo log会记录自己的事务id,所以自己再次查询也是可见的
回到问题
RC和RR的差别在于,RC每次查询都会生成新的readview,RR只有事务开启的时候会生成readview,所以在RC隔离级别,是会产生幻读的,而在RR隔离级别,因为readview的存在,并不需要依赖锁机制去保障数据的可见性。
总结
对应标题总结一下:
- MVCC:Multiversion concurrency control(多版本并发控制),是MySQL保障数据可见性的手段
- 快照读:是MVCC下的具体读取操作
- undo log:是快照读真正读取的快照数据
- readview:是判断undo log可见性规则的依赖数据
嗯,没错,搁这套娃呢