美文网首页
MySQL到底在RR层面解决幻读了吗?

MySQL到底在RR层面解决幻读了吗?

作者: 编了个程 | 来源:发表于2021-06-18 23:00 被阅读0次

    Y说

    hi,好久不见。

    最近工作上有一些变动,文章很少更新。不过平时还是有收集一些文章idea,后面有空会慢慢写~

    这篇文章是因为之前写了一篇关于InnoDB锁的文章,在个人网站上有读者留言问“间隙锁一定程度上解决了幻读问题,为什么不是完全解决了呢”,所以重新把MySQL中关于幻读的问题梳理了一遍。

    文章写完后,发现自己也有新的收获,又了解了一些奇怪的知识点,哈哈。

    隔离级别与幻读

    数据库的事务有四种隔离级别,这四种隔离级别分别应用在不同的场景,隔离级别越低,并发性越高,但数据一致性就越差。四种隔离级别存在的问题如下表:

    隔离级别 脏读 不可重复读 幻读
    Read uncommitted
    Read Committed
    Repeatable Reads
    Serializable

    MySQL InnoDB默认的隔离级别是Repeatable Reads(RR),但它通过MVCC+间隙锁解决了绝大部分幻读(后面会解释为什么是绝大部分而不是全部)的问题。

    什么是幻读

    简单来说,一个事务同样的查询条件,两次查询到的数据行数不一样,它就产生了幻读。

    举例来说,对于下面的数据:

    id c d
    5 5 5
    10 10 10
    15 15 15

    事务A第一次查询sql:

    SELECT * FROM demo WHERE id < 10;
    

    这个时候只能查出 id = 5 的数据。

    而这个时候如果事务B插入了一条id = 6的数据:

    INSERT INTO demo VALUES (7, 7, 7);
    

    然后A进行第二次查询,用同样的sql,如果查到了两条数据:id = 5 和 id = 7,那就是出现了幻读。对于事务A来说,就像幻觉一样,同样的查询条件,查出来的数据多了一行。

    这个在业务场景中也很常见:先查询名为“编了个程”的公众号,发现不存在,于是我准备创建的时候,却发现已经被其它事务创建了,于是唯一索引提示我创建失败,改名称已存在。那我这个事务岂不是出现幻觉了?明明刚刚查过不存在的呀~

    快照读和当前读

    快照读

    在MySQL中,查询语句分为两种,一种是简单的select操作,属于快照读,不加锁。它读的是记录的快照版本(这个版本跟MVCC有关,后面再细聊)。

    select * from table where ?;
    

    当前读

    另一种是要加锁的特殊的读操作,它读的是记录的最新版本。

    -- 共享读锁
    select * from table where ? lock in share mode;
    -- 共享写锁
    select * from table where ? for update;
    
    -- 增删改也属于当前读,因为要先看这条记录在不在
    insert into table values (…);
    update table set ? where ?;
    delete from table where ?;
    

    快照读下的幻读

    InnoDB是通过MVCC来解决当前读下的幻读问题的。每个事务会有一个递增的事务ID,每行记录都有两个隐藏字段:创建版本和删除版本。在进行操作时,遵循以下规则:

    INSERT

    保存当前事务id作为行版本号

    DELETE

    保存当前的事务id到这行数据的“删除版本”。

    UPDATE

    插入一行新记录,保存当前事务id作为行版本号,同时保存当前事务id到原来的行的“删除版本”。

    SELECT

    • 只读取版本小于等于当前事务id的行。这样可以保证事务读取都的行,要么之前就存在,要么是这个事务本身自己插入或者修改的。
    • 只读取“删除版本”为空或者小于等于当前事务id的行。这样可以确保事务读取到的行,在事务之前没有被删除。

    当前读下的幻读

    当前读下的幻读是通过间隙锁(gap_lock)来实现的。在事务A查询的时候,会锁住一个间隙,其它事务往这个间隙插入、删除等操作都是会被锁阻塞的。

    关于间隙锁是如何工作的,可以参考我之前写的这篇文章:《InnoDB的行锁,原来为你做了这么多!》,简单来说,间隙锁和插入意向锁互斥,彻底解决了当前读下的幻读问题。

    快照读没有完全解决幻读?

    是的,MySQL没有完全解决快照读下的幻读问题。

    可以做这个实验:

    1. 当前DB已有id 5, 10, 15三条数据。

    2. 事务A查询id < 10的数据,可以查出一行记录id = 5

    3. 事务B插入id = 6的数据

    4. 事务A再查询id < 10的数据,可以查出一行记录id = 5,查不出id = 6的数据(读场景,解决了幻读)

    5. 事务A可以更新/删除id = 6的数据,不能插入id = 6的数据(写场景,幻读不彻底)

    这个很好理解,MySQL虽然通过MVCC的版本号来解决了读场景下的幻读,但对于上面第5步那种写场景的情况,其实是无能为力的,因为MVCC毕竟是无锁实现。

    所以如果后续要对数据进行写操作,还是通过for update语句上锁比较稳妥,不然就可能会出现上面第5步那样的问题。

    求个支持

    我是Yasin,一个坚持技术原创的博主,我的微信公众号是:编了个程

    都看到这儿了,如果觉得我的文章写得还行,不妨支持一下。

    文章会首发到公众号,阅读体验最佳,欢迎大家关注。

    你的每一个转发、关注、点赞、评论都是对我最大的支持!

    还有学习资源、和一线互联网公司内推哦

    相关文章

      网友评论

          本文标题:MySQL到底在RR层面解决幻读了吗?

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