InnoDB RR隔离级别下解决幻读
# InnoDB RR隔离级别下解决幻读
# 演示
A | B |
---|---|
begin; | begin; |
select *from award where id>2; | |
insert into award (id) values(6); | |
commit; | |
select *from award where id>2; | |
commit; |
第一次select
id|award_id|award_type|award_name|award_content|create_time |update_time |
--+--------+----------+----------+-------------+-------------------+-------------------+
3|3 | 1|ipad |Code |2021-08-15 15:38:05|2021-08-15 15:38:05|
4|4 | 1|AirPods |Code |2021-08-15 15:38:05|2021-08-15 15:38:05|
5|5 | 1|Book |Code |2021-08-15 15:38:05|2021-08-15 15:38:05|
2
3
4
5
第二次select
id|award_id|award_type|award_name|award_content|create_time |update_time |
--+--------+----------+----------+-------------+-------------------+-------------------+
3|3 | 1|ipad |Code |2021-08-15 15:38:05|2021-08-15 15:38:05|
4|4 | 1|AirPods |Code |2021-08-15 15:38:05|2021-08-15 15:38:05|
5|5 | 1|Book |Code |2021-08-15 15:38:05|2021-08-15 15:38:05|
2
3
4
5
A | B |
---|---|
begin ; | begin; |
select *from award where id>2; | |
insert into award (id) values(6); | |
commit; | |
update award set award_type =2 where id = 6; | |
select *from award where id>2; | |
commit; |
第一次select
id|award_id|award_type|award_name|award_content|create_time |update_time |
--+--------+----------+----------+-------------+-------------------+-------------------+
3|3 | 1|ipad |Code |2021-08-15 15:38:05|2021-08-15 15:38:05|
4|4 | 1|AirPods |Code |2021-08-15 15:38:05|2021-08-15 15:38:05|
5|5 | 1|Book |Code |2021-08-15 15:38:05|2021-08-15 15:38:05|
2
3
4
5
6
第二次select
id|award_id|award_type|award_name|award_content|create_time |update_time |
--+--------+----------+----------+-------------+-----------------------+-----------------------+
3|3 | 1|ipad |Code | 2021-08-15 15:38:05| 2021-08-15 15:38:05|
4|4 | 1|AirPods |Code | 2021-08-15 15:38:05| 2021-08-15 15:38:05|
5|5 | 1|Book |Code | 2021-08-15 15:38:05| 2021-08-15 15:38:05|
6| | 2| | |2023-08-18 14:54:19.492|2023-08-18 14:54:19.492|
2
3
4
5
6
7
# RR隔离级别下解决幻读的原理
RR 隔离级别下只会出现两种读:当前读,快照读。是不会像读未提交那样不加锁读和不通过MVCC来读历史版本的,其是完全不加锁的读(虽然也只是普通的select,但是rc与rr的普通select就是会通过MVCC来读历史版本(快照读),串行化的普通select都是会自己加锁的(当前读)
针对快照读(普通 select 语句),是通过 MVCC 方式解决了幻读,在快照读的情况下,RR 隔离级别只会在事务开启后的第一次查询生成
Read View
,并使用至事务提交。所以在生成Read View
之后其它事务所做的更新、插入记录版本对当前事务并不可见,实现了可重复读和防止快照读下的 “幻读”。针对当前读(select ... for update 等语句),是通过 next-key lock(记录锁+间隙锁)方式解决了幻读,因为当执行 select ... for update 语句的时候,会加上 next-key lock,如果有其他事务在 next-key lock 锁范围内插入了一条记录,那么这个插入语句就会被阻塞,无法成功插入,所以就很好了避免幻读问题。
# 发生幻读的场景与原因
以上两点解决幻读的方式,当前读是没有问题的,因为是加锁读。但是快照读是可能存在幻读发生的的。
针对快照读(普通 select 语句),是通过 MVCC 方式解决了幻读,在可重复读隔离级别下,事务 A 第一次执行普通的 select 语句时生成了一个 ReadView,之后事务 B 向表中新插入了一条 id = 5 的记录并提交。接着,事务 A 对 id = 5 这条记录进行了更新操作,在这个时刻,版本链中会插入这条新记录的历史版本 并且trx_id 隐藏列的值为事务 A 的事务 id,之后事务 A 再使用普通 select 语句去查询这条记录时就可以看到这条记录了(根据可见性算法的读取规则),于是就发生了幻读。
针对快照读(普通 select 语句),是通过 MVCC 方式解决了幻读,那么如果此时另一个事务跟新了数据,而我们读的时候选择了当前读,就不会去读通过MVCC读版本链,会直接读到跟新的数据。
# 总结
要避免这类特殊场景下发生幻读的现象的话,就是尽量在开启事务之后,马上执行 select ... for update 这类当前读的语句,因为它会对记录加 next-key lock,从而避免其他事务插入一条新记录。