mysql可重复读的幻读解决方案

首先需要明确的就是“幻读”概念:隔离级别是可重复读,在一个事务中前后两次查询,查到了其他事务insert进来的数据。
强调的是读取到了其他事务插入进来的数据。
下面来论证一下可重复读下幻读的解决方案

# 建表语句
CREATE TABLE `test`  (
  `id` int(11) NOT NULL COMMENT '主键',
  `d` int(11) NOT NULL,
  `c` int(11) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_bin COMMENT = '测试表';
# 插入数据
INSERT INTO `test`(`id`, `d`, `c`) VALUES (0, 0, 0);
INSERT INTO `test`(`id`, `d`, `c`) VALUES (2, 2, 2);
INSERT INTO `test`(`id`, `d`, `c`) VALUES (3, 3, 3);
INSERT INTO `test`(`id`, `d`, `c`) VALUES (4, 4, 4);
INSERT INTO `test`(`id`, `d`, `c`) VALUES (5, 5, 5);
INSERT INTO `test`(`id`, `d`, `c`) VALUES (6, 6, 6);
INSERT INTO `test`(`id`, `d`, `c`) VALUES (7, 7, 7);
INSERT INTO `test`(`id`, `d`, `c`) VALUES (8, 8, 8);
INSERT INTO `test`(`id`, `d`, `c`) VALUES (9, 9, 9);
T1 T2 T3
begin; select * from TABLE where d = 5 for update;
update TABLE set d = 5 where id = 0;
select * from TABLE where d = 5 for update;
insert into TABLE values(1,5,1);
select * from TABLE where d = 5 for update; commit;

先明确一下,for update语法就是当前读,也就是查询当前已经提交的数据,并且是带悲观锁的。没有for update就是快照读,也就是根据readView读取的undolog中的数据。

  • 当T1开启事务,执行第一条select语句时,查出来的结果如下:
id d c
5 5 5
  • 假如该查询只锁定一行,也就是id=5的这一行数据,那么在T2阶段id=0并不会被锁定,那么update执行成功后,id=0那行数据被设置为d=5。此时T1阶段第二个select查询结果如下:
id d c
5 5 5
0 5 0
  • T3阶段执行插入语句后,T1阶段第三个查询得出的结果如下:
id d c
5 5 5
0 5 0
1 5 1

如果按照以上猜想,那么整个执行结果就违背了可重复读的隔离级别了。


那么我们再假设select * from TABLE where d = 5 for update;这条语句锁定的是所有被扫描到的数据。

  • 按照上述执行逻辑,我们在T1阶段第一个select读取到的数据:
id d c
5 5 5
  • T1阶段第二个select读取到的数据:
id d c
5 5 5

这是因为T2阶段的update会被阻塞住,毕竟所有被扫描到的记录都被锁定了。

  • 但是在T3阶段依然会执行,T3阶段做的是insert操作,本身这条记录在表中都不存在的,也就不会被阻塞。那么T1阶段第三个select查询结果如下:
id d c
5 5 5
1 5 1

按照上述推理过程,很显然,即使锁定所有扫描到的数据行,也依然存在幻读的情况。违背了可重复读的隔离级别。


针对这个情况,我们要解决幻读的问题,那么就要求针对所有被扫描的记录行以及还不存在的d=5的记录行都给锁住。

  • 在T1阶段当执行第一条select语句时,所有被扫描的记录行都锁住,包括d=5的不存在的记录行。那么T2执行的时候,就会被阻塞住,等待T1结束。
id d c
5 5 5
  • 当执行到T1阶段的第二个select时,因为T2还在等待T1结束,所以查询结果一样
id d c
5 5 5
  • 当执行T3阶段的insert语句时,因为所有d=5的不存在的记录行也被锁住了,也就是间隙被锁住了,那么T3的insert语句也被阻塞,等待T1结束。那么T1阶段最后一条select语句执行结果如下:
id d c
5 5 5

至此,当前查询结果完全满足可重复读的隔离级别。


通过以上推论,我们可以总结一下,在可重复读的隔离级别下,解决幻读除了需要锁定所有扫描到的记录行外,还需要锁定行之间的间隙,也就是通过间隙锁来解决幻读的问题。

你可能感兴趣的:(mysql可重复读的幻读解决方案)