MySQL undo日志精讲3-从回滚段中申请 Undo 页面链表

回滚段-Rollback Segment Header 页面

回滚段的概念

我们现在知道一个事务在执行过程中最多可以分配4个 Undo 页面链表,在同一时刻不同事务拥有的 Undo 页面链表是不一样的,所以在同一时刻系统里其实可以有许许多多个 Undo 页面链表存在。为了更好的管理这些链表,设计InnoDB的大佬又设计了一个称之为 Rollback Segment Header 的页面,在这个页面中存放了各个 Undo 页面链表的 first undo page 的页号,他们把这些页号称之为 undo slot
在这里插入图片描述
设计InnoDB的大佬规定,每一个 Rollback Segment Header 页面都对应着一个段,这个段就称为Rollback Segment,翻译过来就是回滚段(注意不是 undo log segment)。与我们之前介绍的各种段不同的是,这个Rollback Segment里其实只有一个页面(这可能是设计InnoDB的大佬们的一种洁癖,他们可能觉得为了某个目的去分配页面的话都得先申请一个段,或者他们觉得虽然目前版本的MySQL里Rollback Segment里其实只有一个页面,但可能之后的版本里会增加页面也说不定)。

TRX_RSEG_MAX_SIZE:本Rollback Segment中管理的所有Undo页面链表中的Undo页面数量之和的最大值。
	换句话说,本Rollback Segment中所有Undo页面链表中的Undo页面数量之和不能超过TRX_RSEG_MAX_SIZE代表的值。
    该属性的值默认为无限大,也就是我们想写多少Undo页面都可以。
    小贴士:无限大其实也只是个夸张的说法,4个字节能表示最大的数也就是0xFFFFFFFF,
    但是我们之后会看到,0xFFFFFFFF这个数有特殊用途,
    所以实际上TRX_RSEG_MAX_SIZE的值为0xFFFFFFFE。
TRX_RSEG_HISTORY_SIZE:History链表占用的页面数量。
TRX_RSEG_HISTORY:History链表的基节点。
	小贴士:History链表后边讲,稍安勿躁。
TRX_RSEG_FSEG_HEADER:
	本Rollback Segment对应的10字节大小的Segment Header结构,通过它可以找到本段对应的INODE Entry。
TRX_RSEG_UNDO_SLOTS:
	各个Undo页面链表的first undo page的页号集合,也就是undo slot集合。
	一个页号占用4个字节,对于16KB大小的页面来说,
	这个TRX_RSEG_UNDO_SLOTS部分共存储了1024个undo slot,所以共需1024 × 4 = 4096个字节。

从回滚段中申请Undo页面链表

初始情况下,由于未向任何事务分配任何Undo页面链表,所以对于一个Rollback Segment Header页面来说,它的各个undo slot都被设置成了一个特殊的值:FIL_NULL(对应的十六进制就是0xFFFFFFFF),表示该undo slot不指向任何页面。

随着时间的流逝,开始有事务需要分配Undo页面链表了,就从回滚段的第一个undo slot开始,看看该undo slot的值是不是FIL_NULL:

  • 如果是FIL_NULL,那么在表空间中新创建一个段(也就是Undo Log Segment ,上篇提到每一个Undo页面链表都对应着一个段,称之为Undo Log Segment),然后从段里申请一个页面作为 Undo 页面链表的 first undo page,然后把该 undo slot 的值设置为刚刚申请的这个页面的地址,这样也就意味着这个undo slot被分配给了这个事务。
  • 如果不是 FIL_NULL,说明该 undo slo t已经指向了一个undo链表,也就是说这个 undo slot 已经被别的事务占用了,那就跳到下一个undo slot,判断该undo slot的值是不是FIL_NULL,重复上面的步骤。

一个Rollback Segment Header页面中包含1024个undo slot,如果这1024个undo slot的值都不为FIL_NULL,这就意味着这1024个undo slot都已经名花有主(被分配给了某个事务),此时由于新事务无法再获得新的Undo页面链表,就会回滚这个事务并且给用户报错:

Too many active concurrent transactions

用户看到这个错误,可以选择重新执行这个事务(可能重新执行时有别的事务提交了,该事务就可以被分配Undo页面链表了)。

当一个事务提交时,它所占用的undo slot有两种命运:

如果该undo slot指向的Undo页面链表符合被重用的条件(就是我们上面说的Undo页面链表只占用一个页面并且已使用空间小于整个页面的3/4)。该 undo slot 就处于被缓存的状态,设计InnoDB的大佬规定这时该Undo页面链表的 TRX_UNDO_STATE 属性(该属性在 first undo page 的 Undo Log Segment Header 部分)会被设置为 TRX_UNDO_CACHED。

被缓存的undo slot都会被加入到一个链表,根据对应的Undo页面链表的类型不同,也会被加入到不同的链表:
如果对应的Undo页面链表是insert undo链表,则该undo slot会被加入insert undo cached链表。
如果对应的Undo页面链表是update undo链表,则该undo slot会被加入update undo cached链表。

一个回滚段就对应着上述两个cached链表,如果有新事务要分配undo slot时,
先从对应的cached链表中找。如果没有被缓存的undo slot,才会到回滚段的 Rollback Segment Header页面中再去找。

如果该undo slot指向的Undo页面链表不符合被重用的条件,那么针对该undo slot对应的Undo页面链表类型不同,也会有不同的处理:和 MVCC 紧密相关

如果对应的Undo页面链表是insert undo链表,
	则该Undo页面链表的 TRX_UNDO_STATE 属性会被设置为 TRX_UNDO_TO_FREE,
	之后该Undo页面链表对应的段会被释放掉(也就意味着段中的页面可以被挪作他用),
	然后把该undo slot的值设置为FIL_NULL。

如果对应的 Undo 页面链表是update undo链表,
	则该Undo页面链表的 TRX_UNDO_STATE 属性会被设置为 TRX_UNDO_TO_PRUGE,则会将该undo slot的值设置为FIL_NULL,
	然后将本次事务写入的一组undo日志放到所谓的 History 链表中
   (需要注意的是,这里并不会将Undo页面链表对应的段给释放掉,因为这些undo日志还有用呢~)。
    参考:[MySQL MVCC精讲之 purge]

多个回滚段

我们说一个事务执行过程中最多分配 4 个 Undo 页面链表,而一个回滚段里只有1024个 undo slot,很显然 undo slot的数量有点少啊。我们即使假设一个读写事务执行过程中只分配1个Undo页面链表,那 1024 个 undo slot 也只能支持1024个读写事务同时执行,再多了就崩溃了。这就相当于会议室只能容下1024个班长同时开会,如果有几千人同时到会议室开会的话,那后来的那些班长就没地方坐了,只能等待前面的人开完会自己再进去开。

话说在InnoDB的早期发展阶段的确只有一个回滚段,但是设计InnoDB的大佬后来意识到了这个问题,咋解决这问题呢?会议室不够,多盖几个会议室不就得了。所以设计InnoDB的大佬一口气定义了128个回滚段,也就相当于有了128 × 1024 = 131072 个 undo slot。假设一个读写事务执行过程中只分配1个Undo页面链表,那么就可以同时支持131072个读写事务并发执行(这么多事务在一台机器上并发执行,还真没见过呢~)。
  每个回滚段都对应着一个Rollback Segment Header页面,有128个回滚段,自然就要有128个Rollback Segment Header页面,这些页面的地址总得找个地方存一下吧!于是设计InnoDB的大佬在系统表空间的第5号页面的某个区域包含了128个8字节大小的格子:
在这里插入图片描述每个8字节的格子的构造就像这样:
  
在这里插入图片描述如果所示,每个8字节的格子其实由两部分组成:

4字节大小的Space ID,代表一个表空间的ID。
4字节大小的Page number,代表一个页号。  
也就是说每个8字节大小的格子相当于一个指针,指向某个表空间中的某个页面,这些页面就是Rollback Segment Header。
这里需要注意的一点事,要定位一个Rollback Segment Header还需要知道对应的表空间ID,这也就意味着不同的回滚段可能分布在不同的表空间中。

所以通过上面的叙述我们可以大致清楚, 在系统表空间的第5号页面中存储了 128 个 Rollback Segment Header 页面地址,每个 Rollback Segment Header 就相当于一个回滚段。在Rollback Segment Header页面中,又包含1024个undo slot,每个undo slot都对应一个Undo页面链表。我们画个示意图:
  在这里插入图片描述

回滚段的分类----向Undo页面写入undo日志本身也是一个写页面的过程

我们把这128个回滚段给编一下号,最开始的回滚段称之为第0号回滚段,之后依次递增,最后一个回滚段就称之为第127号回滚段。这128个回滚段可以被分成两大类:

第0号、第33~127号回滚段属于一类。其中第0号回滚段必须在系统表空间中(就是说第0号回滚段对应的Rollback Segment Header页面必须在系统表空间中),第33~127号回滚段既可以在系统表空间中,也可以在自己配置的undo表空间中,关于怎么配置我们稍后再说。如果一个事务在执行过程中由于对普通表的记录做了改动需要分配Undo页面链表时,必须从这一类的段中分配相应的undo slot。

第1~32号回滚段属于一类。这些回滚段必须在临时表空间(对应着数据目录中的ibtmp1文件)中。

如果一个事务在执行过程中由于对临时表的记录做了改动需要分配Undo页面链表时,必须从这一类的段中分配相应的undo slot。也就是说 如果一个事务在执行过程中既对普通表的记录做了改动,又对临时表的记录做了改动,那么需要为这个记录分配2个回滚段,再分别到这两个回滚段中分配对应的undo slot。

不知道大家有没有疑惑,为什么要把针对普通表和临时表来划分不同种类的回滚段呢?这个还得从Undo页面本身说起,我们说Undo页面其实是类型为 FIL_PAGE_UNDO_LOG 的页面的简称,说到底它也是一个普通的页面。我们前面说过,在修改页面之前一定要先把对应的 redo 日志写上,这样在系统奔溃重启时才能恢复到奔溃前的状态。我们向Undo页面写入undo日志本身也是一个写页面的过程,设计InnoDB的大佬为此还设计了许多种redo日志的类型,比方说MLOG_UNDO_HDR_CREATE、MLOG_UNDO_INSERT、MLOG_UNDO_INIT等等等等,也就是说我们对Undo页面做的任何改动都会记录相应类型的redo日志。但是对于临时表来说,因为修改临时表而产生的undo日志只需要在系统运行过程中有效,如果系统奔溃了,那么在重启时也不需要恢复这些undo日志所在的页面,所以在写针对临时表的Undo页面时,并不需要记录相应的redo日志。总结一下针对普通表和临时表划分不同种类的回滚段的原因:在修改针对普通表的回滚段中的Undo页面时,需要记录对应的redo日志,而修改针对临时表的回滚段中的Undo页面时,不需要记录对应的redo日志。

undo 日志在崩溃时的作用

在服务器因为崩溃而恢复的过程中, 首先需要按照 redo 日志将各个页面的数据恢复到崩溃之前的状态,这样可以保证已经提交的事务的持久性。但是这里仍然存在一个问题,就是 那些没有提交的事务写的 redo 日志可能也已经刷盘,那么这些未提交的事务修改过的页面在 MySOL 服务器重启时可能也被恢复了。为了保证事务的原子性,有必要在服务器重启时将这些未提交的事务回滚掉。那么,怎么找到这些未提交的事务呢? 这个工作又落到了 undo 日志头上。我们可以通过系统表空间的第5号页面定位到 128 个回滚段的位置,在每一个回滚段的1,024 个 undo slot 中找到那些值不为 FIL NULL 的 undo slot,每一个 undo slot 对应着一个Undo 页面链表。然后从 Undo 页面链表第一个页面的 Undo Log Segment Header 中找到 TRXUNDO STATE 属性,该属性标识当前 Undo 页面链表所处的状态。如果该属性的值为 TRXUNDO ACTIVE,则意味着有一个活跃的事务正在向这个 Undo 页面链表中写入 undo 日志,然后再在 Undo Segment Header 中找到 TRX UNDO LAST LOG 属性,通过该属性可以找到本Undo 页面链表最后一个 Undo Log Header 的位置。从该 Undo Log Header 中可以找到对应事务的事务 id 以及一些其他信息,则该事务id 对应的事务就是未提交的事务。通过 undo 日志中记录的信息将该事务对页面所做的更改全部回滚掉,这样就保证了事务的原子性。

redo 日志最终是需要写在磁盘里的,
undo 日志是存在内存里的,但是写 undo 页对应 的 redo 是在磁盘里的

什么时候释放 undo日志,参考:MySQL MVCC精讲之 purge

  • 13
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值