概述
笔者在学习数据库相关内容时,发现关于innoDB在RR级别下究竟能不能保证不发生幻读这个问题,网上的资料众说纷纭,笔者在经过总结和自己的试验之后,在这里结合自己的理解分析一下这个问题,若有谬误,欢迎指正。笔者在这里默认读者都了解了关于幻读以及innoDB中MVCC和锁机制的情况,仅对该问题进行分析。
先说结论,innoDB的RR级别下仍然会出现幻读的情况,但是innoDB还是通过MVCC和锁尽可能避免幻读发生。
MVCC做了什么
首先,关于MVCC,在RR级别下,MVCC只会在事务的首个查询发生时生成一个Read View,后续的相同查询都是共用这个Read View,这样就实现了可重复读。同时不难想到,这也解决了幻读,因为即使另一个事务成功插入并提交,该版本在首次生成的Read View中也是不可见的。但是我们要强调的是,这仅仅是解决了快照读的幻读问题。如果事务中只有快照读,那么RR级别下不会有幻读问题。
锁做了什么
其次,innoDB通过间隙锁来尽量避免幻读问题,这里简单举一个网上常见的例子
CREATE TABLE `test_RR` (
`id` INT NOT NULL,
`teacher_id` INT NULL,
`class_id` INT NULL,
`name` VARCHAR(6) NULL,
PRIMARY KEY (`id`),
INDEX `teacher_id` (`teacher_id` ASC))
ENGINE = InnoDB
DEFAULT CHARACTER SET = utf8;
transaction A | transaction B | |
---|---|---|
1 | begin; | |
2 | select * from test_RR where teacher_id=1; | |
3 | update test_RR set name='zhang' where teacher_id=1; | |
4 | begin; | |
5 | insert into test_RR values (12,2,6,'zhang'); |
经过测试,这里的语句5会被阻塞,因为语句3 update是当前读操作,给表中加了间隙锁,和B的插入操作互斥。
为啥幻读:
然而,还有一种情况会导致幻读
transaction A | transaction B | |
---|---|---|
1 | begin; | |
2 | select * from test_RR where teacher_id=1; | |
3 | begin; | |
4 | insert into test_RR values (12,1,6,'zhang'); | |
5 | commit; | |
6 | update test_RR set name='wang' where teacher_id=1; | |
7 | select * from test_RR where teacher_id=1; | |
8 | commit ; |
语句2结果:
语句7结果:
这里还是产生了幻读问题,关键在于语句6 update是当前读,故而将事务B插入提交的行也读了进来进行了更新,最要命的是让该行有了一个版本号为当前事务id的版本,这样语句7查询的时候自然就将它读了进来,导致了幻读问题。
参考资料:
面试被反问,RR级别下能解决幻读问题那为什么不叫幻读级别?_扭秧歌的一只泱的博客-CSDN博客
文章评论