MVCC详细讲解

今天小编给大家分享的是MVCC的详细介绍,相信很多人都不太了解,为了让大家更加了解MVCC,所以给大家总结了以下内容,话不多说,一起往下看吧。
  1. MVCC的实现,是通过保存数据在某个时间点的快照来实现的,也就是说,不管需要执行多长时间,每个事务看到的数据是一致的,根据事务开始的时间不同,每个事务对同一张表,同一时刻看到的数据可能是不一样的。
  2. innodb在基于锁的并发控制技术上,实现了MVCC技术,innodb的MVCC技术有以下几个特点:
    • 事务的标识,依靠事务ID,是一个全局唯一的64bits数值
    • 多版本,是元组级的多版本,而不是oracle实现的是页面级的多版本
    • 最新的数据存储在数据页面中,其他数据的旧版本存储在回滚段中
  3. 因为innodb的多版本是元组级的版本,所以在每个记录上,有一些与并发和回滚等于事务相关的隐含字段
    • DATA_TRX_ID: 6字节长,表示上一个执行插入或更新操作的事务
    • DATA_ROLL_PTR: 7字节长,表示旧版本的数据位于回滚段中的位置,指向的是一个旧版本,只有元组被更新,才有会新版本产生,旧版本被置于回滚段,因此一致性无锁读操作按照“read view”快照需要读取旧版本时,只能根据事务ID回到回滚段中寻找旧版本
    • DATA_ROW_ID:6字节长,表示执行插入操作后生成的单调自增长的行的ID标识,如果存在聚集索引,索引项则包括的是这个DB_ROW_ID值
    • DELETE_BIT: 删除标志位
  4. 位于回滚段中的UNDO日志分为两种
    • INSERT UNOD LOGS:插入到回滚段的日志,仅用于事务提交时使用,当事务提交,则对应的INSETR UNDO LOGS里面的内容被清除
    • UPDATE UNDO LOGS:被用于一致性无锁读,为一致性读提供快照隔离下的可被读取的老版本数据,当没有需要满足一致性读的快照时,一些老版本数据才被清理
  5. MVCC的工作机制
    • innodb的MVCC,是通过在每行记录后面保存两个隐藏的列来实现的,这两个列,一个保存的行的创建时间,一个保存行的过期时间,当然存储的并不是实际的时间值,而是系统版本号(system version number),每开始一个新的事务,系统版本号就会递增,事务开始时刻的系统本号会作为事务的版本号,用来和查询到的每行记录的版本号进行记录,下面看一个在REPEATABLE READ隔离级别下,MVCC是如何操作的
    • SELECT
      • innodb会根据以下两个条件检查每行记录,只有符合下面两个条件的记录,才能返回作为查询结果
        1. innodb值只查找系统早已当前事务版本的数据行(也就是行的系统版本号小于或等于事务的系统版本号),这样可以确保事务读取的行,要么是在事务开始前就已经存在的,要不是事务自身插入或者修改过的
        2. 行的删除版本要不未定义,要不大于当前事务版本号,这可以确保事务读取到的行,在事务开始之前未被删除
    • INSERT
      • innodb为新插入的每一行保存当前系统版本号作为行版本号
    • DELETE
      • innodb为删除的每一行保存当前系统版本号作为行删除标识
    • UPDATE
      • innodb为插入一条新纪录,保存当前系统版本号作为行版本号,同时保存当前系统的版本号到原先的行作为删除标识
  6. 保留着两个额外系统版本号,使得大多数读操作读不需要加锁。这样设计使得读数据操作很简单,性能很好,并且也能保证只会读取到符合标准的行,不足之处是每行记录都需要额外的存储空间,需要做更多的行检查工作,以及一些额外的维护工作
  7. MVCC只在REPEATABLE READ和READ COMMIT两个隔离级别下工作,其他两个隔离级别都和MVCC不兼容,因为READ UNCOMMIT总是读取最新的数据行,而不是符合当前事务版本的数据行,而SERIALIZEABLE则会对所有读取的行都加锁
  8. 下面我们就开始案例:
    *MVCC详细讲解
    • 我们执行step7的时候发现一个问题,怎么id=4的查不出来呢,按照上面的规则,id=3的事务ID是2,T3的事务ID是3,这种情况id=4是可以查出来的呢,所以说上面的规则肯定还说的不是很详细,有遗落的地方,我们接着往下面看
  9. 我们在事务隔离中可以知道,innodb在执行select的时候会创建一个快照
    • 隔离级别大于等于可重复读:事务块的所有的SELECT操作都要使用同一个快照,此快照是在第一个SELECT操作时建立的
    • 隔离级别小于等于已提交读:事务块内的所有的SELECT操作分别创建属于自己的快照,因此每次读都不同,后面的SELECT操作的读就可以读到本次读之前已经提交的数据
    • 当前数据可以看到哪些数据就是由这个快照决定的,快照有下面几个属性,并且可见性也根据这几个属性来判断的
      • m_up_limit_id   一个快照,有左右边界,左边界是最小值,右边界是最大值,此变量是左边界
      • m_low_limit_id   右边界
      • m_createor_trx_id   正在创建事务的事务ID
      • trx_ids 快照创建时,处于活动即尚未完成的读写事务的集合
    • 举一个列子:新建一个快照,假设当前事务的事务ID为6,这时候读写事务链上(这个是全局的)活动的事务有{3,5,6,10},不包括只读事务,那么调用方法创建快照时就会把{3,5,10}存储到当前的视图中的trx_ids(6因为是当前事务的ID,不记录到视图中),m_up_limit_id的值是3,m_low_limit__id是10,m_createor_trx_id是6
      • trx_id
      • trx_id>m_low_limit_id 不可见,意味着是快照之后发生的事务
      • trx_ids对应的事务左右的修改还处于活动状态即还没有提交对当前事务来说不可见
      • trx_id=m_createor_trx_id 可见
      • 公式:通过比较事务ID的值是否在[m_up_limit_id,m_low_limit_id]区间的左侧,中间,还是右侧判断是否可见
        • 左侧   可见
        • 中间
          • 在trx_ids中不可见
          • 等于m_createor_trx_id 可见
        • 右侧  不可见
  10. innodb的MVCC技术中的多版本是根据UNDO日志来实现了,我们在上面说过,DATA_ROLL_PTR ,表示旧版本的数据位于回滚段中的位置。DB_ROLL_PTR的结构如下
    • MVCC详细讲解
  11. 多版本生成
    • 对于一个逻辑上的多版本生成过程,其方式如下
      • 最老的版本,一定是插入操作暂存到UNDO日志的版本(对于聚集索引,不是记录的所有字段读暂存到回滚段,而是主键信息被暂存)
      • 更新操作,把旧值存入UNDO日志。同一个日志反复被更新,则每次读存入一个旧值(前像)到UNDO日志内,如此就会有多个版本,版本之间,使用DATA_POLL_PTR执行根据的版本,由此所有版本构成一个链表,链头是索引上的记录,链尾是首次插入时生成的UNDO信息。
      • 删除操作,在UNDO日志中保存删除标志
  12. 多版本查找
    • 对于聚集索引,可以根据DATA_ROLL_PTR就可以从回滚段中找出前一个版本的记录,并知道此记录是更新操作还是插入操作生成的,如果是插入操作生成的,则意味着此版本是最原始的版本,即使不可见也没有必要在继续回溯查找旧版本了
    • 但是查找的过程与隔离级别紧密相关
      • 如果是未提交读隔离级别,根本不去找旧版本,在索引上读到的记录就被直接使用
      • 如果不是未提交隔离级别,则需要进入UNDO回滚段中根据DATA_POLL_PTR进行查找,还要判断是否可见,如果可见,则返回,如果不可见,则一直根据DATA_POLL_PTR进行查找
    1. 有了前面的补充,我们看下是否能解释前面step7出现的问题

      创新互联是一家专注于成都网站设计、网站建设与策划设计,仪陇网站建设哪家好?创新互联做网站,专注于网站建设十年,网设计领域的专业建站公司;建站业务涵盖:仪陇等地区。仪陇做网站价格咨询:028-86922220

      • MVCC详细讲解
      • MVCC详细讲解
      • 我们在T2的step8修改了id=1的title,但step 9,step10没有看到,step9没看到,大家不会有什么意外,因为step9对应的事务ID小于step8的事务ID,但step10的事务ID是大于step8的事务ID的,那么我们来分析一下是什么造成的,我们这里不说undo log的格式,我们只需要知道根据undo log我们可以知道记录的多个版本
      • 一开始,id=1的记录是这样的
      • MVCC详细讲解
      • T1的step4
        • m_up_limit_id=1,m_low_limit_id=1,m_createor_trx_id=1,trx_ids={},DATA_TRX_ID=0
        • DATA_TRX_ID
      • T2的step 5
        • m_up_limit_id=1,m_low_limit_id=2,m_createor_trx_id=2,trx_ids={1},DATA_TRX_ID=0
        • DATA_TRX_ID
      • T3的step 6
        • m_up_limit_id=1,m_low_limit_id=3,m_createor_trx_id=3,trx_ids={1,2},DATA_TRX_ID=0
        • DATA_TRX_ID
      • step7修改了数据,这时候id=1的记录是这样的
        • MVCC详细讲解
      • T2的step8
        *m_up_limit_id=1,m_low_limit_id=2,m_createor_trx_id=2,trx_ids={1},DATA_TRX_ID=2
        • DATA_TRX_ID在[m_up_limit_id,m_low_limit_id]的中间位置,m_createor_trx_id=DATE_TRX_ID,因此可见。所以看到了title=‘商品1 push’的这一行
      • T1的step9
        • m_up_limit_id=1,m_low_limit_id=1,m_createor_trx_id=1,trx_ids={},DATA_TRX_ID=2
        • DATA_TRX_ID在[m_up_limit_id,m_low_limit_id]的右侧,记录不可见
        • 那么接着通过DATE_ROLL_PTR,去undo找到了trx_id=0的这一行
        • DATA_TRX_ID=0,DATA_TRX_ID
      • step10
        • m_up_limit_id=1,m_low_limit_id=3,m_createor_trx_id=3,trx_ids={1,2},DATA_TRX_ID=2
        • DATA_TRX_ID在[m_up_limit_id,m_low_limit_id]的中间位置,并且在trx_ids中,记录不可见
        • 接着通过DATE_ROLL_PTR,去undo找到了trx_id=0的这一行
        • DATA_TRX_ID=0, DATA_TRX_ID=0,DATA_TRX_ID

      关于MVCC详细讲解就分享到这里了,希望以上内容可以对大家有一定的参考价值,可以学以致用。如果喜欢本篇文章,不妨把它分享出去让更多的人看到。


当前标题:MVCC详细讲解
文章源于:http://csdahua.cn/article/jhjpoi.html
扫二维码与项目经理沟通

我们在微信上24小时期待你的声音

解答本文疑问/技术咨询/运营咨询/技术建议/互联网交流