美文网首页MySQL
05.白话MySQL MVCC

05.白话MySQL MVCC

作者: 乐活骑士 | 来源:发表于2021-03-07 16:08 被阅读0次

    事务特性

    大家都知道关系型数据库事务的特性:ACID

    image
    不同的隔离级别,存在着不同问题
    image
    MySQL默认的隔离级别是RR(在生产中我们一般采用RC的隔离级别),可以解决脏读和不可重复读,但是不能解决幻读的问题,如果要解决幻读的问题,就要采用串行化的方式,这样对数据库性能会有很大影响。

    今天咱们来唠唠隔离性中,MySQL InnoDB实现RC和RR的原理——MVCC

    啥是MVCC?

    MVCC看起来很熟悉,难道是MVC的表兄弟?开个玩笑,MVCC的英文全称是Multi Version Concurrency Control,中文是多版本并发控制。从名字看来,InnoDB是通过数据行的多个版本,来管理数据库的并发。这个数据行的版本,有点类似于我们开发代码时的多次commit版本。

    通过 MVCC 我们可以解决以下几个问题:

    1. 读写之间阻塞的问题,通过 MVCC 可以让读写互相不阻塞,即读不阻塞写,写不阻塞读,这样就可以提升事务并发处理能力。
    2. 降低了死锁的概率。这是因为 MVCC 采用了乐观锁的方式,读取数据时并不需要加锁,对于写操作,也只锁定必要的行。
    3. 解决一致性读的问题。一致性读也被称为快照读,当我们查询数据库在某个时间点的快照时,只能看到这个时间点之前事务提交更新的结果,而不能看到这个时间点之后事务提交的更新结果。

    快照读和当前读

    快照读

    快照读顾名思义,就是读取的快照数据,有可能读到历史版本数据。简单的select都是快照读,比如:

    select * from user_info ; 
    

    当前读

    当前读就是读取最新数据,而非历史版本,加锁的select或者增、删、改,都属于当前读:

    select * from user_info for update;
    
    update user_info set name = 'zhangsan' where id = 2;
    
    delete from user_info where id = 2;
    
    insert into user_info values ('zhangsan');
    

    RC和RR是如何读取数据的呢?

    在读已提交和可重复读隔离级别中,多事务并发,查询的并不是当前行最新数据,而是数据库快照。
    读已提交隔离级别下,会在每次查询前,读取快照,所以它是不可重复度。
    可重复读隔离级别下,只会在事务开始时,查询数据库快照,所以它是可重复读。
    那么问题来了,数据库快照是什么?数据库成百上千G的数据,如何能在瞬间生成快照呢?我们继续剖析。

    InnoDB中MVCC如何实现的?

    在深入MVCC前,我们先做一些知识准备。

    事务号

    InnoDB会为每个事务分配一个版本号——trx_id,这个版本号是递增的,可以以此判断事务开启的时间。
    细心的同学可能会问,如果trx_id用完了怎么办,这里其实大可放心,trx_id的设计可以使用到各位开发者安享晚年,它是采用6字节无符号数据存储,最大值为2的48次方=281474976710656,如果按照每秒10wTPS,可以安全使用89年,既是用到最后一位后,便会从0开始,这样有可能会产生一些脏读,不过基本上不会发生。

    行记录的隐藏字段

    每个人的身上(数据行)是都有毛毛(隐藏字段)
    我来给你唱(分析下)毛毛(隐藏字段)
    到底我们身上都有些什么毛(隐藏字段)
    我来唱(分析)给你们知道

    InnoDB的叶子节点上存储的是数据页,数据页上存储着数据行,行里面存着我们定义的字段,除了我们定义的字段外,还有一些隐藏字段,用于InnoDB系统计算。

    image

    行数据

    image

    Undo Log

    InnoDB将快照数据,记录在了Undo Log中,我们可以在回滚段中找到相关数据,如图:

    image

    根据图来看,roll_ptr指针,将undo log用链表结构组合在一起,每个undo log都记录了当时的trx_id,这样我们可以很方便的找到历史快照。

    Read View

    有了这些前置条件,我们来探讨下,在RC和RR隔离级别下,InnoDB是如何通过MVCC实现一致性读的。
    InnoDB中,多个事务对同一行数据更新,会产生多个历史版本(就像git
    commit记录一样),这些历史版本就记录在Undo Log中,如果某个事务想要查询某行数据,那它需要读取哪行数据呢?这时候Read View登场了。

    image

    有了这个read view,我们需要找哪个快照版本的数据,就一目了然了,那到底怎么找,我们看图,一图胜千言:

    image

    假设当前活跃事务是trx_id_8、trx_id_10、trx_id_12、trx_id_20,那么套用变量:

    1. up_limit_id = trx_id_8
    2. low_limit_id = trx_id_20
    3. trx_ids = [trx_id_8, trx_id_10, trx_id_12, trx_id_20]

    假设我们在事务中需要查询的数据的trx_id为x,那么:

    1. x < 8,表示该数据快照是当前活跃事务提交前的版本,是已经完成的事务提交的,安全可以使用。
    2. x > 20,表示该数据快照版本是当前活跃事务之后事务提交的,是不可以使用的。
    3. 若:8 <= x <= 20
      3.1 trx_ids.contain(x),该行数据未提交,不可见。
      3.2 !trx_ids.contain(x),该行数据已提交,可见。

    好了,那么通过这种方式,InnoDB实现了全库数据快照,并保证了高并发下数据的准确性和一致性。设计思路不得不佩服。

    总结

    MVCC的核心可以拆分为MV + VCMV通过Undo Log来实现多版本的管理,VC通过Read View来实现事务并发下,数据是否可见,针对不同的隔离级别,产生Read View的时机也不一样,这样也就实现了不同的隔离级别RC和RR。
    MVCC在不通的数据库下,实现的原理不一样,主要是理解设计思想。

    完,谢谢大家阅读

    image

    相关文章

      网友评论

        本文标题:05.白话MySQL MVCC

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