自学内容网 自学内容网

MySQL多版本并发(MVCC)机制

系列文章目录

一、MySQL数据结构选择
二、MySQL性能优化explain关键字详解
三、MySQL索引优化
四、MySQL事务
五、MySQL锁机制
六、MySQL多版本并发(MVCC)机制


一、MVCC概述

  MVCC是为了解决多个事务并发执行时可能出现的数据一致性和冲突问题而产生的机制。如果用传统方式解决数据一致性和冲突问题,可以通过加锁,但是无论是乐观锁还是悲观锁,对于性能都有一定的损耗,尤其是在并发冲突较多的情况下。而MVCC对一行数据的读和写两个操作默认是不会通过加锁互斥来保证隔离性,避免了频繁加锁互斥。
  MySQL的读已提交可重复读隔离级别下都实现了MVCC机制。

二、undo日志版本链

  在MVCC实现中,数据库中的每一行数据通常是一个链式结构,其中包含当前版本和历史版本的链接。这个版本链就是通过Undo日志来管理的。每当某个事务修改数据时,数据库不会直接修改数据行,而是创建一个新的数据版本,并将旧的版本通过Undo日志记录下来。
  在undo日志版本链中,除了维护修改记录的行数据,还有TRX_ID(事务ID),ROLL_POINTER(回滚指针)。其中事务ID记录了操作该条记录的事务唯一ID,而回滚指针指向上一条记录,以此形成链状结构:
在这里插入图片描述  假设此时事务ID为2的事务对该条数据进行修改,回滚指针指向前一条记录。(无论事务是否提交,只要对数据进行修改,该条数据的日志版本链就会增加)
在这里插入图片描述  后续对该条数据有修改,都会记录版本链,以此类推:
在这里插入图片描述  无论事务是否提交,只要有修改操作,InnoDB都会创建新的数据行版本并添加到版本链中。如果事务被提交,那么这些修改会被正式保存,版本链会持续记录修改的历史。如果事务没有提交,修改会被回滚,版本链上的这些数据会被删除,不会成为持久化数据的一部分。并且begin/start transaction 命令并不是一个事务的起点,在执行到它们之后的第一个修改操作或加排它锁操作(比如select…for update)的语句,事务才真正启动,才会向mysql申请真正的事务id,mysql内部是严格按照事务的启动顺序来分配事务id的。

二、Read view算法

  在可重复读隔离级别,当事务开启,执行任何查询sql时会生成当前事务的一致性视图read-view,该视图在事务结束之前永
远都不会变化(如果是读已提交隔离级别在每次执行查询sql时都会重新生成read-view),这个视图由执行查询时所有未提交事务id数组(数组里最小的id为min_id)和已创建的最大事务id(max_id)组成,事务里的任何sql查询结果需要从对应版本链里的最新数据开始逐条跟read-view做比对从而得到最终的快照结果。
在这里插入图片描述图片来源:图灵学院

  版本链比对规则:

  1. 如果 row 的 trx_id 落在绿色部分( trx_id<min_id ),表示这个版本是已提交的事务生成的,这个数据是可见的;
  2. 如果 row 的 trx_id 落在红色部分( trx_id>max_id ),表示这个版本是由将来启动的事务生成的,是不可见的(若 row 的
    trx_id 就是当前自己的事务是可见的);
  3. 如果 row 的 trx_id 落在黄色部分(min_id <=trx_id<= max_id),那就包括两种情况
    • 若 row 的 trx_id 在视图数组中,表示这个版本是由还没提交的事务生成的,不可见(若 row 的 trx_id 就是当前自己的
      事务是可见的);
    • 若 row 的 trx_id 不在视图数组中,表示这个版本是已经提交了的事务生成的,可见。

2.1、可重复读的Read view

  假设目前有三个事务,其执行情况如下:
在这里插入图片描述  对应的版本链如下:
在这里插入图片描述  在select1进行查询操作时,会从版本链的最后一条向上比对。根据规则,最后一条的trx_id在未提交与已提交事务的区间内,并且trx_id 300 不在[100,200]的视图数组中,表示这个版本是已经提交了的事务生成的,可见。所以balance的最终结果就是trx_id 300 对应的2000。
  后续事务A重新更新了两次,并且提交了事务,事务B更新了一次,没有提交事务。
在这里插入图片描述
  版本链如下,在可重复读的隔离级别下,read view是不会变的。
在这里插入图片描述
  在select1进行查询操作时,会从版本链的最后一条向上比对。根据规则,最后一条的trx_id在未提交与已提交事务的区间内,并且trx_id 200 在[100,200]的视图数组中,表示这个版本是由还没提交的事务生成的,不可见(5000的balance不可见),继续向上比对。
  倒数第二条的trx_id在未提交与已提交事务的区间内,并且trx_id 100 在[100,200]的视图数组中,表示这个版本是由还没提交的事务生成的,不可见(4000的balance不可见),继续向上比对。
  倒数第三条的trx_id在未提交与已提交事务的区间内,并且trx_id 100 在[100,200]的视图数组中,表示这个版本是由还没提交的事务生成的,不可见(3000的balance不可见),继续向上比对。
  倒数第四条的trx_id在未提交与已提交事务的区间内,并且trx_id 300 不在[100,200]的视图数组中,表示这个版本是已经提交了的事务生成的,可见。所以balance的最终结果就是trx_id 300 对应的2000。
  由上述的过程可知在可重复读隔离级别下,每次select语句查询到的都是第一次读取结果的原因。

2.2、读已提交的Read view

  在读已提交隔离级别,每次每次执行查询sql时都会重新生成read-view
在这里插入图片描述  读已提交的版本链:
在这里插入图片描述  根据规则从最后一条向上对比,最后一条的trx_id在未提交与已提交事务的区间内,并且trx_id 200 在[200]的视图数组中,表示这个版本是由还没提交的事务生成的,不可见(5000的balance不可见),继续向上比对。
  倒数第二条的trx_id不在未提交与已提交事务的区间内,表示这个版本是已提交的事务生成的,这个数据是可见的。(4000的balance可见)。
  由上述的过程可知在读已提交隔离级别下,每次select语句查询到的都是最新事务提交结果的原因。


原文地址:https://blog.csdn.net/2301_77599076/article/details/145081563

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!