美文网首页
mysql可重复读的幻读解决方案

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

作者: 梦想实现家_Z | 来源:发表于2021-08-01 22:27 被阅读0次

    首先需要明确的就是“幻读”概念:隔离级别是可重复读,在一个事务中前后两次查询,查到了其他事务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可重复读的幻读解决方案

          本文链接:https://www.haomeiwen.com/subject/otorvltx.html