16.Mysql之MVCC浅谈03

1.前言

     Mysql的MVCC机制是Mysql中的重要的一环,其中也是经常在Mysql DBA面试中被问到,并且也是Innodb存储引擎(带有MVCC机制)和Myism存储引擎的一个很大的区别。

2. 什么是MVCC技术?

  MVCC(Multi-Version Concurrency Control)即多版本并发控制,在高性能Mysql一书中介绍,MVCC的大多数事务型存储引擎实现都不是简单的行级锁,它们一般都实现了多版本并发控制,可以认为MVCC是行锁的一个变种,在很多情况下避免的加锁的操作,因此开销更低,实现对读的不阻塞,读不加锁,读写不冲突。缺点是每行记录需要额外的记录空间,需要做更多的维护工作和检查

  MVCC的实现是通过保存数据在某个时间点的快照来实现的,也就是说不管执行多长时间,每个事务看到的数据都是一致的,根据事务开始的时间点不同,每个事务对同一张表,同一时刻看到的数据都是不一样的,

3.MVCC具体实现原理?

  MVCC具体实现原理主要是依赖记录中3个隐藏的字段、undo日志、以及Read View来实现的

  3.1. 三个隐藏字段:在表中除了我们自定义的字段外,还包括DB_TRX_ID,DB_ROLL_PTR,DB_ROW_ID等字段

  • DB_TRX_ID:6byte,最近修改(修改/插入)事务ID:记录创建这条记录/最后一次修改该记录的事务ID
  • DB_ROLL_PTR:7byte,回滚指针,指向这条记录的上一个版本(存储于rollback segment里)
  • DB_ROW_ID:6byte,隐含的自增ID(隐藏主键),如果数据表没有主键,InnoDB会自动以DB_ROW_ID产生一个聚簇索引
  • 实际还有一个删除flag隐藏字段, 既记录被更新或删除并不代表真的删除,而是删除flag变了 

如下图所示: 

前面的两列是用户自己定义的,后面的三列是Mysql数据库默认的隐藏列,其中,DB_ROW_ID是数据库默认为该行记录生成的唯一隐式主键,DB_TRX_ID是当前操作该记录的事务ID,而DB_ROLL_PTR是一个回滚指针,用于配合undo日志,指向上一个旧版本

  3.2 undo日志

  undo日志主要分为两种:

  • insert undo log : 代表事务在insert新记录时产生的undo log, 只在事务回滚时需要,并且在事务提交后可以被立即丢弃
  • update undo log:事务在进行update或delete时产生的undo log; 不仅在事务回滚时需要,在快照读时也需要;所以不能随便删除,只有在快速读或事务回滚不涉及该日志时,对应的日志才会被purge线程统一清除

  对mvcc有帮助的主要是:update undo log日志,它实际存在于rollback segment中旧的记录链表中,

  执行流程如下(很重要):

  比如一个有个事务插入persion表插入了一条新记录,记录如下,name为LiuYiFei, age为20岁,隐式主键是1,事务ID和回滚指针,我们假设为NULL,

  

   现在来了一个事务1对该记录的name做出了修改,改为Tom (update person set name=Tom where  name='LiuYiFie')

  • 在事务1对数据行进行修改时,首先数据库会对该数据行加一个排他锁
  • 然后会把该行数据拷贝到undo log中,作为旧的记录,这样unlog 里面就会有了改行的副本
  • 拷贝完毕之后,修改字段name有LiuYiFei-->Tom,并修改隐藏字段事务ID为当前事务为1的ID,这里默认从1开始,之后递增,回滚指针指向拷贝拷贝到undo log的副本记录,即表示我的上一个副本就是它。
  • 事务提交,释放锁

如下图所示:

 

 

  又来了个事务2修改person表的同一个记录,将age修改为30岁

  • 在事务2对数据行进行修改时,首先数据库也会对该数据行加一个排他锁
  • 然后把该行数据拷贝到undo log中,作为旧记录,发现该行记录已经有undo log了那么最新的旧数据作为链表的表头,插在该行记录的undo log最前面
  • 修改该行age为30岁,并且修改隐藏字段的事务ID为当前事务2的ID, 那就是2,回滚指针指向刚刚拷贝到undo log的副本记录
  • 事务提交,释放锁

  从上面可以看出,不同事务或者相同事务对同一记录的修改,会导致该记录的undo log成为一条记录版本线性表,既链表,undo log的链首就是最新的旧记录,链尾就是最早的旧记录(当然就像之前说的该undo log的节点可能是会purge线程清除掉,向图中的第一条insert undo log,其实在事务提交之后可能就被删除丢失了,不过这里为了演示,所以还放在这里)

  purge:

  • 从前面的分析可以看出,为了实现InnoDB的MVCC机制,更新或者删除操作都只是设置一下老记录的deleted_bit,并不真正将过时的记录删除
  • 为了节省磁盘空间,InnoDB有专门的purge线程来清理deleted_bit为true的记录。为了不影响MVCC的正常工作,purge线程自己也维护了一个read view(这个read view相当于系统中最老活跃事务的read view);如果某个记录的deleted_bit为true,并且DB_TRX_ID相对于purge线程的read view可见,那么这条记录一定是可以被安全清除的。

 3.3 Read View(读视图)

    什么是读视图?    

    说白了Read View就是事务进行快照读操作的时候生成的读视图(Read View),在该事务执行的快照读的那一刻,会生成数据库系统当前的一个快照,记录并维护系统当前活跃事务的ID(当每个事务开启时,都会被分配一个ID, 这个ID是递增的,所以最新的事务,ID值越大)
    
    所以 Read View主要是用来做可见性判断的, 即当某个事务执行快照读的时候,对该记录创建一个Read View读视图,把它比作条件用来判断当前事务能够看到哪个版本的数据,既可能是当前最新的数据,也有可能是该行记录的undo log里面的某个版本的数据
 
    InnoDB为每个事务构造了一个数组,用来保存这是事务启动的瞬间,当前正在活跃的所有的事务ID,活跃指的是,事务启动了但是还没有提交。
    这个数组里面的ID的最小值记为低水位,当前系统里面已经创建过的事务ID的最大值加1记为高水位。
    这个视图数组和高水位,就组成了当前事务的一致性视图:
    而数据版本的可见性规则,就是基于数据的trix_id和这个一致性视图做对比的结果得到的。
    这个视图数组把所有的trx_id分成了三种情况:
 
    
    这样,对于当前事务的启动瞬间来说,一个数据版本的row trx_id,就会有三种情况:
  • 如果落在绿色的部分,表示这个版本是已提交的事务或者是当前事务自己生成的是可见的
  • 如果落在红色的部分,表明这个版本是由将来事务生成的,是肯定不可见的
  • 如果落在黄色的部分,那么就包括两种情况:
    • 若row_trx_id在数组中,表示这个版本是由还没提交的事务生成的,不可见
    • 若row_trx_id不在这个数组中,表示这个版本是已经提交的事务生成的,可见。  
 

    当前读:    

    像select lock in share mode(共享锁), select for update ; update, insert ,delete(排他锁)这些操作都是一种当前读,为什么叫当前读?就是它读取的是记录的最新版本,读取时还要保证其他并发事务不能修改当前记录,会对读取的记录进行加锁。

    快照读:

    像不加锁的select操作就是快照读,即不加锁的非阻塞读;快照读的前提是隔离级别不是串行级别,串行级别下的快照读会退化成当前读;之所以出现快照读的情况,是基于提高并发性能的考虑,快照读的实现是基于多版本并发控制,即MVCC,可以认为MVCC是行锁的一个变种,但它在很多情况下,避免了加锁操作,降低了开销;既然是基于多版本,即快照读可能读到的并不一定是数据的最新版本,而有可能是之前的历史版本

    总结:

    说白了MVCC就是为了实现读-写冲突不加锁,而这个读指的就是快照读, 而非当前读,当前读实际上是一种加锁的操作,是悲观锁的实现
 

posted on 2020-11-01 18:00  太白金星有点烦  阅读(85)  评论(0编辑  收藏  举报

导航