MySQL学习(17)MVCC

发布时间 2023-11-15 10:34:27作者: 哪过晓得

前言

在聚簇索引中,每一条记录中包含trx_id和roll_pointer隐藏列。

  • trx_id存储了该记录最近一次修改时的事务id。
  • roll_pointer存储了该记录最近一次修改时产生的undo日志的地址。

undo日志中记录了修改前的数据,并且除了insert操作产生的undo日志外,对于update、delete操作中对应的undo日志,都记录了上一条undo日志的地址。

什么是MVCC

每次更新记录后,会将旧值存放到一条undo日志中,随着更新次数的增多,所有的版本都会被roll_pointer属性连接成一个链表,这个链表称为版本链。链表的头节点是当前记录的最新值。每个版本的undo日志还包含对应的事务id。

MVCC(Multi-Version Concurrency Control)多版本并发控制:就是利用记录的版本链来控制并发事务访问相同记录时的行为。

2107

MVCC如何执行的

事务在访问记录时,会创建一个ReadView结构的视图,为创建该试图时当前系统中关于事务的一些最新的统计信息。ReadView包含如下信息:

  • m_ids:当前系统中活跃的事务id列表。
  • min_trx_id:当前十五中活跃的最小事务id。
  • max_trx_id:系统分配给下一个事务的事务id值。
  • creator_trx_id:生成ReadView的事务的事务id。
  • trx_no:系统分配给下一个完成事务提交的事务no值。

如果事务并没有对表中记录进行修改,则事务id为0,creator_trx_id也为0。

聚簇索引并发访问执行过程

  1. 生成ReadView。
  2. 从聚簇索引中找到这条记录,判断记录中的trx_id属性值
    1. 如果trx_id等于ReadVIew中的creator_trx_id值,表示这条记录是该事务自己修改的,可直接访问该记录。
    2. 如果trx_id值小于ReadView中的min_trx_id,表示修改这条记录的事务一定已经提交了,可以直接访问该记录。
    3. 如果trx_id值大于或等于ReadView中的max_trx_id,表示修改这条记录的事务在创建ReadVIew后开启的,不可以访问该记录。
    4. 如果trx_id值大于等于min_trx_id且小于max_trx_id时,trx_id存在于m_ids列表中,表示修改记录的事务还处于活跃状态,不可以访问该记录;如果trx_id不在m_ids列表中,可以访问该记录。
  3. 如果该记录不可以直接访问,就根据roll_pointer找到undo日志中上一个版本的记录,重复上述步骤,知道找到可以访问的版本。如果知道最后一个版本也不可访问,查询结果就不包含该记录。

READ COMMITTED隔离级别下每次读取数据前都生成一个ReadView,可能出现不可重复读。

REPEATABLE READ隔离级别下只在第一次读取数据时生成一个ReadView,事务开始后不管查询多少次,使用的ReadView都是第一次查询时创建的,所以查询结果都一样。

二级索引并发访问执行过程

trx_id和roll_pointer只存在于聚簇索引中,在二级索引中可以通过页面中的Page Header部分名为PAGE_MAX_TRX_ID的属性,这个属性记录了页面中的记录进行修改操作的最大事务id。

访问二级索引记录的过程:

  1. 找到二级索引记录所在的页的Page Header部分PAGE_MAX_TRX_ID的属性,如果PAGE_MAX_TRX_ID属性值小于ReadVIew中min_trx_id,表示页面中所有的记录都可以访问;否则执行步骤2。
  2. 通过二级索引记录中的主键进行回表操作,得到聚簇索引记录后再按照聚簇索引的访问过程找到ReadView可访问的版本,然后判断该版本中相应的二级索引列的值是否与利用二级索引查询时的值相同。如果相同,则可访问该记录;如果不相同,则跳过该记录。

注意:MVCC是对一般的SELECT查询生效。

为MVCC提供支持的undo日志的生存周期

insert undo日志在事务提交后就可以被删除了,但是update undo日志还需要支持MVCC,不能立即被删除。

什么是History链表?每个回滚段都对应一个名为History的链表,一个事务在某个回滚段中写入的一组update undo日志在该事务提交后,就会加入到回滚段的History链表中。

一个事务写入的一组undo日志中都有一个Undo Log Header部分,其中有一个TRX_UNDO_HISTORY_NODE属性,表示History链表的节点。当一个事务提交后,就会把这个事务执行过程中产生的这一组update undo日志插入到History链表的头部。

每个回滚段都对应一个Rollback Segement Header页面,这个页面有如下属性:

  • TRX_RSEG_HISTORY:表示History链表的基节点。
  • TRX_RSEG_HISTORY_SIZE:表示History链表占用的页面数量。

delete mark操作仅仅是将记录的记录头信息中的deleted_flag属性设置为1,并没有真正删除这条记录。在一组undo日志中Undo Log Header部分有一个TRX_UNDO_DEL_MARKS属性,标记本组undo日志中是否包含delete mark操作产生的undo日志。系统维护了一个线程,用来执行purge操作。当MVCC不再需要用到这些undo日志时,就可以将它们真正的删除掉了。

思路:只要生成ReadView时某个事务已经提交了,那么该ReadView肯定就不需要访问该事务运行过程中产生的undo日志了。

  • 在事务提交时,根据事务提交的时间顺序,生成一个事务no值。一组undo日志对应的Undo Log Header部分有一个名为TRX_UNDO_TRX_NO的属性记录了这个事务no值。History链表中的undo日志也是按照事务的提交顺序排序,相当于按照事务no排序。
  • ReadView中的trx_no表示系统中最大的事务no值还要大1的值。当前系统中所有的ReadView按照创建时间连成一个链表。执行purge操作时,就根据最早生成的ReadView中的事务no。从各个回滚段中取出History链表中的undo日志。如果一组undo日志的事务no值小于最早生成的ReadView中的事务no,就可以将其从History链表中移出,并真正删除释放空间。如果该组undo日志TRX_UNDO_DEL_MARKS为1,存在delete mark删除的记录,需要将对应的标记删除的记录彻底删除。