• MySQL进阶系列:多版本并发控制 MVCC


     

    mysql是目前互联网中用的最广泛的关系型数据库,InnoDB是mysql默认的存储引擎也是使用最多的存储引擎,能够满足大多数的业务需求,其中高并发的优点就是通过mvcc实现的。这篇文章就来介绍下mvcc是如何支持并发的。

    多版本并发控制

    MVCC 全称Multi-Version Concurrency Control,MVCC是一种通过增加版本冗余数据来实现并发控制的方法,一般在数据库管理系统中,实现对数据库的并发访问,在编程语言中实现事务内存。

    mysql中的InnoDB中实现了MVCC主要是为了提高数据库的并发性能,在无锁的情况下也能处理读写并发,大大提高数据库的并发度。

     

    首先我们有一张表,业务字段如下

    -- id只是一个普通字段,并不是主键
    mysql> select * from ajisun;
    +------+--------+--------+
    | id   | name   | city   |
    +------+--------+--------+
    | 100  | ajisun | 上海    |
    +------+--------+--------+
    1 row in set (0.00 sec)
     

    下面我们通过以下几个关键字的解释来说明mvcc

    当前读,快照读,三个隐藏字段,undo日志,read view,版本链, RR和RC隔离基本下的区别

    一. 当前读,快照读

    当前读

    当前读获取的数据是最新数据,而且在读取时不能被其他修改的,所以会对读取的记录加锁来控制。如下

    select * from ajisun where id > 1 lock in share mode;
    或者
    select * from ajisun where id >1 for update;

     

    快照读

    简单的select查询就是快照读,不加锁非阻塞读,降低数据库的开销。

    但是快照读在隔离级别是串行化级别是没有意义的,因为串行化的sql都是排队执行的,不存在并发,所以就会变成当前读。

    快照读,顾名思义读取的是一份快照数据,所以读到的并不一定是最新数据,可能是历史数据。

     

    mvcc的实现原理主要是依赖三个隐藏字段,undo日志,read view

     

    二,隐藏字段

    除了我们正常业务涉及的字段外,InnoDB在内部向数据库表中添加三个隐藏字段:

    • DB_TRX_ID:6-byte的事务ID。插入或更新行的最后一个事务的事务ID

    • DB_ROLL_PTR:7-byte的回滚指针。就是指向对应某行记录的上一个版本,在undo log中使用。

    • DB_ROW_ID:6-byte的隐藏主键。如果数据表中没有主键,那么InnoDB会自动生成单调递增的隐藏主键(表中有主键或者非NULL的UNIQUE键时都不会包含 DB_ROW_ID列)。

    如上面的表没有设计primary key,其中id/name/city是我们的业务字段,那么加上隐藏字段应该如下

      

    三,undo log和版本链

    undo log就是回滚日志,在insert/update/delete变更操作的时候生成的记录方便回滚。

    当进行insert操作的时候,产生的undo log只有在事务回滚的时候需求,如果不回滚在事务提交之后就会被删除。

    当进行update和delete的时候,产生的undo log不仅仅在事务回滚的时候需要,在快照读的时候也是需要的,所以不会立即删除,只有等不在用到这个日志的时候才会被mysql purge线程统一处理掉(delete操作也只是打一个删除标记,并不是真正的删除)。

    所谓的版本链就是多个事务操作同一条记录的时候都会生成一个undo日志,这些undo日志通过回滚指针串联在一起。

    例如现在一个事务id10向表中插入一条记录,如下

    现在有第二个事务id=20的事物来修改这条记录,name改成“纪先生”,此事务会对数据库中这条记录加排他锁,然后把这行记录拷贝到undo log 中,拷贝完成之后修改这行记录name为“纪先生”,同时修改隐藏字段的事务id为当前事务20的id,回滚指针指向刚刚复制在undo log中的位置提交事务,释放锁。

    其他事务对这行记录修改也是这样的操作流程,所有事务拷贝的副本都是放在undo log的表头,这样就形成了一个版本链(版本链的头节点就是当前记录的最新值),如下

    四,ReadView

    ReadView 是事务快照读的时候产生的数据读视图,在该事务执行快照读的那一刻,会生成一个数据系统当前的快照,记录并维护系统当前活跃事务的id,事务的id值是递增的。

    ReadView 的最大作用就是判断数据的可见性,当某个事务执行快照读的时候,会对此记录创建一个ReadView 的视图,在整个事务期间根据某些条件判断该事务能够看到的版本链上的哪条历史数据。

    可见性的判断主要是通过四个全局属性,他们分别是:

    m_ids:表示在生成ReadView 时当前系统中活跃事务的事务id列表。

    m_low_limit_id:表示在生成ReadView 时当前系统应该分配给下一个事务的事务id(也就是还未分配的事务id 即最大事务id+1)

    m_up_limit_id:表示在生成 ReadView 时当前系统中活跃的读写事务中最小的 事务id ,也就是 m_ids 中的最小值。

    m_creator_trx_id:表示生成该 ReadView 的事务的 事务id

    访问某条记录的时候就是根据这四个字段来判断记录的某条版本是否可见:

    1. 如果被访问记录的版本事务ID与ReadView 中的m_creator_trx_id值相同,那么表示当前事务访问的是自己修改过的记录,那么该版本对当前事务可见;

    2. 如果被访问版本的 事务ID小于 ReadView 中的m_up_limit_id的值,那么表示生成该版本的事务在当前事务生成 ReadView 前已经提交,所以该版本可以被当前事务访问。

    3. 如果被访问版本的事务ID大于 ReadView 中的m_low_limit_id 值,那么表示生成该版本的事务在当前事务生成 ReadView 后才开启,所以该版本不可以被当前事务访问。

    4. 如果被访问版本的 事务ID在 ReadView 的m_up_limit_id和m_low_limit_id 之间,那就需要判断一下版本的事务ID是不是在 m_ids 列表中,如果在,说明创建 ReadView 时生成该版本的事务还是活跃的,该版本不可以被访问;如果不在,说明创建 ReadView 时生成该版本的事务已经被提交,该版本可以被访问。

    如果某个版本对当前事务不可见,那么顺着版本链找到下个版本记录,然后继续上面的对比规则,直到找到版本链中的最后一个版本,如果最后一个版本都不可见,那么该条记录对此事务完全不可见,也就查不到这个记录。

    关于mvcc对于版本的可见性,其对比规则就是这样的

     

    五,RC和RR的隔离级别下快照读有什么区别(也就是生成ReadView的不同)

    先说结论:READ-COMMITTED(RC)和REPEATABLE-READ(RR)级别下ReadView不同原因就是生成的时机不同

    在READ- UNCOMMITTED隔离级别下,可以读取到其他事务未提交的数据,直接读最新的就行了,不存在快照读ReadView。

    在SERIALIZABLE隔离级别下,通过加锁的方式让所有sql都串行化执行了,也是读最新的,不存在快照读ReadView。

    在RR级别下的某个事务的对某条记录的第一次快照读会创建ReadView,生成的时候ReadView中就记录了其四个属性,包括活跃事务列表,此后在调用快照读的时候,还是使用的是同一个ReadView,不会重新生成,所以只要当前事务在其他事务提交更新之前使用过快照读,那么之后的快照读使用的都是同一个Read View,所以对之后的修改不可见。

    在RC级别下,事务中,每次快照读都会新生成一个快照和ReadView,这就是我们在RC级别下的事务中可以看到别的事务提交的更新的原因。

      

    六,总结

    面试官:你了解mysql中mvcc么,说一说怎么实现的?

    • mvcc 是多版本并发控制,通过生成记录的历史版本解决幻读问题,并提高数据库的性能,无锁实现读写并发操作。

    • mvcc 的实现主要是通过三个隐藏字段,undo log以及readView 实现的。

    • 三个隐藏字段分别是隐藏主键,事务ID,回滚指针。

    • undo log是各个事务修改同一条记录的时候生成的历史记录,方便回滚,同时会生成一条版本链。

    • readView是事务在进行快照读的时候生成的记录快照,用于判断数据的可见性。

    • readView 可见性判断规则。

    按照上面描述就没啥问题了。

     

    《mysql是怎样运行的》

    《高性能 MySQL》

     


    关注不迷路

    MySQL高级相关更多内容,如事务,锁,MVCC,读写分离,分库分表等还在持续更新中,欢迎关注催更。

    我是阿纪,用输出倒逼输入而持续学习,持续分享技术系列文章,以及全网值得收藏好文,欢迎关注公众号,做一个持续成长的技术人。

     

    MySQL系列的历史文章

    1. MySQL进阶系列:一文了解mysql基础架构

    2. MySQL进阶系列:一文了解mysql存储引擎

    3. MySQL进阶系列:mysql中MyISAM和InnoDB有什么区别;

    4. MySQL进阶系列:mysql中表设计如何更好的选择数据类型;

    5. MySQL进阶系列:数据库设计中的范式究竟该如何使用

    6. MySQL进阶系列:一文详解explain各字段含义

    7. MySQL进阶系列:为什么mysql使用B+作为索引的数据结构

    8. MySQL进阶系列:  你需要知道的一些索引基础知识;

    9. MySQL进阶系列:怎么创建索引更合适;

    10. MySQL进阶系列:主从复制原理和配置;

    11. MySQL进阶系列:join连接的原理-3种算法;

    12. MySQL进阶系列:事务及事务隔离级别;

  • 相关阅读:
    Oracle 更改DBID
    Oracle 修改字段长度
    Oracle 索引
    Oracle在无法打开数据库的状态下获取DBID
    Oracle 备份脚本
    Linux crontab计划任务
    Oracle restore和recovery的区别
    Django基础
    面向对象(一)
    socket
  • 原文地址:https://www.cnblogs.com/sunjiguang/p/15812317.html
Copyright © 2020-2023  润新知