美文网首页
mysql 解决可提交读、可重复读、幻读

mysql 解决可提交读、可重复读、幻读

作者: 定金喜 | 来源:发表于2021-04-20 10:09 被阅读0次

    1.重要的概念

    mysql的锁有哪些


    这张图本人觉得总结得挺好的,在一般的互联网项目中,基本上用的都是Innodb引擎,一般只涉及到的都是行级锁,但是如果sql语句中不带索引进行操作,可能会导致锁表,这是不推荐的,性能非常低,可能会导致全表扫描等,行锁的具体实现算法有以下几种mysql特有的锁:
    Record Lock(记录锁):单个行记录的锁,一般是唯一索引或者主键上的加锁
    Gap Lock(间隙锁):锁定一个区间,但是不包括自身,开区间的锁,RR级别才会有间隙锁,间隙锁的唯一目的是防止区间数据的插入,所以间隙锁与间隙锁之间是不会相互阻塞的
    Next-key Lock(临键锁):与间隙锁的区别是包括自身,是左开右闭区间,RR级别才会有

    加锁规则里面,包含了两个“原则”、两个“优化”和一个“bug”。
    原则 1:加锁的基本单位是 next-key lock,希望你还记得,next-key lock 是前开后闭区间。
    原则 2:查找过程中访问到的对象才会加锁。
    优化 1:索引上的等值查询,给唯一索引加锁的时候,next-key lock 退化为行锁。
    优化 2:索引上的等值查询,向右遍历时且最后一个值不满足等值条件的时候,next-key lock 退化为间隙锁。
    一个 bug:唯一索引上的范围查询会访问到不满足条件的第一个值为止。

    举例来说明上述的原则:
    建表

    CREATE TABLE `t`  (
      `id` int(11) NOT NULL,
      `c` int(11) NULL DEFAULT NULL,
      `d` int(11) NULL DEFAULT NULL,
      PRIMARY KEY (`id`) USING BTREE,
      INDEX `c`(`c`) USING BTREE
    ) ;
    

    插入数据:
    INSERT INTO t(id, c, d) VALUES (0, 0, 0);
    INSERT INTO t(id, c, d) VALUES (5, 5, 10);
    INSERT INTO t(id, c, d) VALUES (10, 10, 10);
    INSERT INTO t(id, c, d) VALUES (15, 15, 15);
    INSERT INTO t(id, c, d) VALUES (20, 20, 20);
    INSERT INTO t(id, c, d) VALUES (25, 25, 25);

    例子1:锁表
    因为d字段上没有建索引,所以涉及该字段的查询加锁会锁住整个表


    锁表

    因为d字段上面没有建立索引,所以事务1执行后会导致整个表被锁,后面所有的操作都会在等待整个表锁被释放

    例子2:主键/唯一索引 记录锁


    唯一索引记录锁

    id字段为主键,而且事务1查询命中了唯一的记录,默认是加Next-key Lock,区间是(0,5],但是根据优化1,唯一索引/主键上的等值查询,会退化为行锁,所以只会锁5这个记录。

    例子3:主键/唯一索引上的间隙锁


    唯一索引的间隙锁

    由于表 t 中没有 id=7 的记录,所以用我们上面提到的加锁规则判断一下的话:根据原则 1,加锁单位是 next-key lock,事务1加锁范围就是 (5,10];同时根据优化 2,这是一个等值查询 (id=7),而 id=10 不满足查询条件,next-key lock 退化成间隙锁,因此最终加锁的范围是 (5,10),所以事务2会阻塞,事务3执行成功。

    例子4:普通索引上的间隙锁


    普通索引上的间隙锁

    c字段是普通索引,事务1执行时默认是对区间(0,5]加间隙锁,根据优化2,非唯一索引/主键会继续向右遍历,找到10,所以最终的加锁为(0,5]的Next-Key锁+(5,10)的间隙锁,所以事务2阻塞,事务3成功。

    例子5:间隙锁与行锁


    间隙锁与行锁

    事务1默认的Next-Key锁区间是(0,5],根据优化2会向右遍历,找到不满足查询条件的10,退化成间隙锁,所以事务1的锁是(0,5]的Next-Key锁+(5,10)的间隙锁,这两个锁与行锁是冲突的,而事务2申请的Next-Key锁是和事务1一样,但是c=5的行锁与事务1冲突,所以产生了阻塞,如果改为update t set d=1000 where c=6;因为此时产生的间隙锁为(5,10),而间隙锁与间隙锁是不冲突的,不会产生阻塞



    这一块自己的理解:Next-Key锁是间隙锁+行锁(记录锁),所以间隙锁与间隙锁之间是不会阻塞的,但是会和行锁冲突,所以间隙锁(0,5]和(0,5)不会产生阻塞,但是(0,5]和行锁5是会产生冲突的,这就解释了上述的例子。

    例子6:lock in share mode锁覆盖索引



    事务1存在覆盖索引的情况,不会去回表,lock in share mode这种情况下只会锁c字段索引,而事务2是对主键加行锁,所以两者不存在冲突。

    例子7:主键/唯一索引上的范围查询



    开始执行的时候,要找到第一个 id=10 的行,因此本该是 Next-Key Lock(5,10],根据优化 1, 主键 id 上的等值条件,退化成行锁,只加了 id=10 这一行的行锁。范围查找就往后继续找,找到 id=15 这一行停下来,因此需要加 Next-Key Lock(10,15],所以事务3是冲突的。

    例子8:普通索引上的范围查询



    开始执行时,找到第一个满足条件的行10,加锁Next-Key Lock(5,10],因为不是唯一索引,所以不会退化,继续向后面找,找到15这一行停下来,因此需要加 Next-Key Lock(10,15],因为是范围查询,所以锁不会退化。

    快照读和当前读

    快照读: 通过MVCC实现,该技术不仅可以保证innodb的可重复读,而且可以防止幻读,但是他读取的数据虽然是一致的,但是数据是历史数据。
    简单的select操作(不包括 select … lock in share mode, select … for update)

    当前读: 要做到保证数据是一致的,同时读取的数据是最新的数据,innodb提供了next-key lock,即gap锁与行锁结合来实现。
    select … lock in share mode
    select … for update
    insert
    update
    delete

    自己理解:
    简单的select是快照读,快照读实现可提交读,可重复读和幻读是通过MVCC+ReadView实现的,而当前读实现这几种是通过锁来实现的,为了说明具体原理,下面介绍下MVCC和ReadView概念,所以简单的select是通过乐观锁实现的,当前读是通过悲观锁实现的。

    MVCC

    参考文章:
    https://www.sohu.com/a/302045871_411876
    https://www.jianshu.com/p/d1aba64b5c03
    https://www.jianshu.com/p/32904ee07e56

    相关文章

      网友评论

          本文标题:mysql 解决可提交读、可重复读、幻读

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