MYSQL-5.MVCC多版本并发控制

admin2024-04-03  0

MVCC多版本并发控制

  1. 目的:主要是为了提高数据库并发性能,更好的处理在并发事物中读-写产生的并发冲突;避免使用锁解决并发冲突,保证读操作在任何时候都是非阻塞的;

原理解析

实现机制

主要通过隐藏字段、undo-log日志、ReadView读视图实现

  1. 隐藏字段DB_ROWID、DB_DELETED_BIT、DB_TRXI_ID、DB_ROLL_PTR):

    1. ROW_ID隐藏主键6Bytes):当不存在一个唯一且非空属性的字段时,会隐式定义一个顺序递增ROW_ID来作为聚簇索引的索引列;

    2. DELETE_BIT删除标识(1Bytes):对于delete语句,当执行sql后并不会立马删除这条数据,而是将DELETE_BIT删除标识改为1,后续sql在检索到这条数据时,不会将DELETE_BIT = 1的数据纳入结果集;

      之后由Mysql的purger线程自动清理DELETE_BIT = 1的数据(purger线程自身也会维护一个ReadView,只会删除DELETE_BIT = 1TRX_IDReadView可见的数据,避免对MVCC的工作产生影响);

      优势:对聚簇索引而言,当事物在删除一条数据后(可能出现节点合并的情况),后续又执行了回滚操作(又插入了一条数据1,可能导致节点分裂),可能存在两次对索引结构的调整;

    3. TRX_ID 最近更新事物的ID(6Bytes):Mysq对于所有包含写入的事物都会分配一个顺序递增的事物ID,若是select语句则事物ID=0;TRX_ID就是记录最近一次改动当前这条数据的事务ID

    4. ROLL_PTR回滚指针(7Bytes):当事物对一条数据做了改动后,会在undo-log中插入一条就版本的数据记录,而ROLL_PTR就是这个记录的地址指针,当需要回滚事物时,可以通过这个地址来回滚找到之前的旧版本数据;

  2. undo-log日志

    1. 存储旧版本的数据,对于某一条数据它会构建出一个通过ROLL_PTR回滚指针作为连接点的单向链表;
    2. update语句时的过程
      • 对修改行的数据加上写锁;
      • 将原本的旧数据拷贝到undo-logrollback segment 区域;
      • 对表数据进行修改,完成后将trx_id改为当前事物ID;
      • ROLL_PRT指向undo-log中对应的旧数据,并在提交事物后释放锁;
    3. 优势:方便实现事物点回滚;实现MVCC机制;
    4. 清除:与delete清除类似;
  3. ReadView读视图

    1. 一个事务在尝试读取一条数据时,基于当前MySQL的运行状态生成的快照

    2. 当一个事物启动后,首次执行select操作时,MVCC会生成数据库的当前ReadView,一般包含:

      1. creator_trx_id:创建这个ReadView的事物ID;
      2. trx_ids:在创建这个ReadView时,系统内活跃的事物ID列表(未结束的事物);
      3. up_limit_id:活跃事物列表中,最小的事物ID;
      4. low_limit_id:表示在生成当前ReadView时,系统中要给下一个事务分配的ID值;
    3. 假设目前数据库中共有T1~T5这五个事务,T1、T2、T4还在执行,T3已经回滚,T5已经提交,此时当有一条查询语句执行时,会生成一个快照的信息如下:
      MYSQL-5.MVCC多版本并发控制,在这里插入图片描述,第1张

      {
          "creator_trx_id" : "0",
          "trx_ids" : "[1,2,4]",
          "up_limit_id" : "1",
          "low_limit_id" : "6"
      }
      

实现原理

  1. 读视图的生成:当事物在执行查询语句时,就先去获取行数据的隐藏列,然后过判断后,可以获得目前查询事物的日志可以获得哪一个版本的事物,如果可以获得最新的则返回表中数据,如果不能则去undo-log中获取旧版本数据返回;

  2. 最新数据访问判断:例子:假设目前存在两个线程T1和T2;

    -- T1: trx_id = 1
    update test set a = "111" where id = 1;
    update test set b = "222" where id = 1;
    
    -- T2: trx_id = 2
    select * from test where id = 1;
    
    1. 当【T2】执行select语句时,会生成一个ReadView
    2. 判断【T2】行数据中的trx_id是否等于【T2】的creator_trx_id也就是事物Id;(读写同事物)
      • 相同:说明修改数据行的事物【T1】与创建读视图的事物【T2】时同一个,可以获得最新数据;
      • 不相同:则说明数据被其他事物修改过;
    3. 比较trx_idup_limit_id最小活跃事物ID;(读时写是否提交)
      • 小于:说明修改事物【T1】在【T2】读视图创建前就已经提交,可以获得最新数据;
      • 不小于:说明修改事物【T1】还在执行;
    4. 比较trx_idlow_limit_id;(读时写是否创建)
      • 大于或等于:说明修改事物【T1】是【T2】创建读视图之后开始的,不能访问最新数据;
      • 小于:需进一步判断,判断trx_id是否在trx_ids中:
        • 在:说明需要改动的事物还在执行【T1】,不能访问最新数据;
        • 不在:说明需要改动的事物执行结束了【T1】,可以访问最新数据;
  3. 旧版本数据访问判断:根据隐藏列roll_ptr找到链表头,然后遍历寻找旧版本数据的trx_id不存在trx_ids活跃事物列表中数据;

  4. 新增情况分析(幻读)

    1. 当事物T1在查询数据时,事物T2突然新增一条数据,此时T2的trx_id会存在于trx_ids中,所以需要去查询旧版本数据,但因为是新增操作,因此回滚指针ROLL_PTR=null,则表明新旧版本数据都无法得到,这条数据对T1事物不可见;

RC、RR隔离级别下的MVCC机制

  1. Read Committed读已提交级别:
    1. 会在每次select语句执行前,生产一个ReadView读视图;存在不可重复读问题;
  2. Repeatable Read可重复读级别:
    1. 会在每个事物第一次执行select语句时生成ReadView,后续在出现select操作不会生成新的ReadView;解决了不可重复读问题;

Repeatable Read可重复读级别下的幻读问题

  1. 对于上述4可以得知,mysql的MVCC机制已经在RR级别下解决了幻读问题,但在极端情况下还是可能出现

    1. 极端场景:假设有两个事物T1、T2,假设表test有3条数据,ID分别为1、2、3;

      事物T1通过select * from test where id > 2,查询id>2数据,此时事物T2通过insert into test values(5, '名称'),插入一条id=5的数据并提交,此时根据上述3.iv可知ID=5这条数据对T1不可见,但若此时事物T1执行update test set name = '名称T1' where ID = 5之后再次执行select * from test where id > 2此时能查询到id = 5这条记录了,这是因为MVCC通过快照去检索数据时,会发现Id = 5这条数据的trx_id是自己,因此此时就能看到这条幻影数据了;

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明原文出处。如若内容造成侵权/违法违规/事实不符,请联系SD编程学习网:675289112@qq.com进行投诉反馈,一经查实,立即删除!