具体细节 请去掘金购买《MySQL 是怎样运行的:从根儿上理解 MySQL》
通用链表结构(页通过这些pageNumebr和offset组成双端链表)
1.在写入undo日志的过程中会使用到多个链表。
2.在某个表空间内,我们可以通过一个页的页号和在页内的偏移量来唯一定位一个节点的位置
3.Pre Node Page Number和Pre Node Offset的组合就是指向前一个节点的指针
4.Next Node Page Number和Next Node Offset的组合就是指向后一个节点的指针。
基节点的结构
1.List Length表明该链表一共有多少节点。
2.First Node Page Number和First Node Offset的组合就是指向链表头节点的指针。
3.Last Node Page Number和Last Node Offset的组合就是指向链表尾节点的指针
FIL_PAGE_UNDO_LOG页面--专门用来存储undo日志
Undo Page Header
1.TRX_UNDO_PAGE_TYPE:本页面准备存储什么种类的undo日志
2.一个Undo页面的只能存储一种类型的undo日志,不能混合存储。
3.类型主要有TRX_UNDO_INSERT_REC和除了TRX_UNDO_INSERT_REC,比如TRX_UNDO_DEL_MARK_REC、TRX_UNDO_UPD_EXIST_REC啥的,一般由DELETE、UPDATE语句产生的undo日志属于这个大类。
4.其中TRX_UNDO_INSERT_REC是由insert语句产生,事务提交后可以删除。而其他的类型的则留作MVCC
5.TRX_UNDO_PAGE_START:表示第一条undo日志在本页面中的起始偏移量。
6.TRX_UNDO_PAGE_FREE:与上边的TRX_UNDO_PAGE_START对应,表示当前页面中存储的最后一条undo日志结束时的偏移量
7.TRX_UNDO_PAGE_NODE:代表一个List Node结构
Undo页面链表
单个事务中的Undo页面链表
1.一个事务中产生的undo日志占用的页面组成了链表,就是TRX_UNDO_PAGE_NODE
2.由于undo日志不能混合存储,因此很有可能产生两个页面链表,一个是insert,一个是update
3.此外规定对普通表和临时表的记录改动时产生的undo日志要分别记录
4.所以一个事务最多有4个以undo页面为节点组成的链表
5.当然,并不是在事务一开始就会为这个事务分配这4个链表,具体分配策略如下:
6.刚刚开启事务时,一个Undo页面链表也不分配。
7.当事务执行过程中向普通表中插入记录或者执行更新记录主键的操作之后,就会为其分配一个普通表的insert undo链表。
8.当事务执行过程中删除或者更新了普通表中的记录之后,就会为其分配一个普通表的update undo链表。
9.当事务执行过程中向临时表中插入记录或者执行更新记录主键的操作之后,就会为其分配一个临时表的insert undo链表。
10.当事务执行过程中删除或者更新了临时表中的记录之后,就会为其分配一个临时表的update undo链表。
多个事务中的Undo页面链表
1.多个事务和单个事务的链表没有区别
2.每个事务都生成自己的链表
undo日志具体写入过程
段(Segment)的概念
1.段是一个逻辑上的概念,本质上是由若干个零散页面和若干个完整的区组成的
2.一个B+数被分为叶子结点段和非叶子节点段。
3.每一个段对应一个INODE Entry结构,这个INODE Entry结构描述了这个段的各种信息,比如段的ID,段内的各种链表基节点,零散页面的页号有哪些等信息
4.为了定位一个INODE Entry,InnoDB设计了一个Segment Header的结构。
Undo Log Segment Header
1.每一个Undo页面链表都对应着一个段,称之为Undo Log Segment
2.也就是说链表中的页面都是从这个段里边申请的
3.Undo页面链表的第一个页面,也就是上边提到的first undo page中设计了一个称之为Undo Log Segment Header的部分
4.这个部分中包含了该链表对应的段的segment header信息以及其他的一些关于这个段的信息
5.Undo Log Segment Header包含的是:
6.属性TRX_UNDO_STATE:本Undo页面链表处在什么状态
7.TRX_UNDO_STATE=TRX_UNDO_ACTIVE:活跃状态,也就是一个活跃的事务正在往这个段里边写入undo日志
8.TRX_UNDO_STATE=TRX_UNDO_CACHED:被缓存的状态。处在该状态的Undo页面链表等待着之后被其他事务重用。
9.TRX_UNDO_TO_FREE:对于insert undo链表来说,如果在它对应的事务提交之后,该链表不能被重用,那么就会处于这种状态
10.TRX_UNDO_TO_PURGE:对于update undo链表来说,如果在它对应的事务提交之后,该链表不能被重用,那么就会处于这种状态
11.TRX_UNDO_PREPARED:包含处于PREPARE阶段的事务产生的undo日志。
12.属性TRX_UNDO_LAST_LOG:本Undo页面链表中最后一个Undo Log Header的位置
13.属性TRX_UNDO_FSEG_HEADER:本Undo页面链表对应的段的Segment Header信息
14.属性TRX_UNDO_PAGE_LIST:Undo页面链表的基节点。
Undo Log Header
1.innodb会按照undo链表来设置,一个链表对应一个组
2在每写入一组undo日志时,都会在这组undo日志前先记录一下关于这个组的一些属性,存储该
属性的地方叫Undo Log Header,每组的undo日志的第一个页面会写上Undo Page Header、Undo Log Segment Header、Undo Log Header这3个部分
3.属性:TRX_UNDO_TRX_ID:生成本组undo日志的事务id
4.属性TRX_UNDO_TRX_NO:事务提交后生成的一个需要序号,使用此序号来标记事务的提交顺序(先提交的此序号小,后提交的此序号大)。
5.属性TRX_UNDO_DEL_MARKS:标记本组undo日志中是否包含由于Delete mark操作产生的undo日志
6.属性TRX_UNDO_LOG_START:表示本组undo日志中第一条undo日志的在页面中的偏移量。
7.属性TRX_UNDO_XID_EXISTS:本组undo日志是否包含XID信息。
8.属性TRX_UNDO_DICT_TRANS:标记本组undo日志是不是由DDL语句产生的。
9.属性TRX_UNDO_TABLE_ID:如果TRX_UNDO_DICT_TRANS为真,那么本属性表示DDL语句操作的表的table id
10.属性TRX_UNDO_NEXT_LOG:下一组的undo日志在页面中开始的偏移量
11.属性TRX_UNDO_PREV_LOG:上一组的undo日志在页面中开始的偏移量。
12.属性TRX_UNDO_HISTORY_NODE:一个12字节的List Node结构,代表一个称之为History链表的节点。
小结
1.对于没有被重用的Undo页面链表来说,链表的第一个页面会填充Undo Page Header、Undo Log Segment Header、Undo Log Header这3个部分
2.对于其他的页面来说,也就是normal undo page在真正写入undo日志前,只会填充Undo Page Header
重用Undo页面
1.因为一个事务就会有创建4个链表的可能,而且这些链表有时候只可能包含一个页面。太浪费了因此需要重用!
2.因此在一下情况,链表可以被重用:
3.该链表中只包含一个Undo页
4.该Undo页面已经使用的空间小于整个页面空间的3/4,对于insert undo链表是直接覆盖,对于update undo链表则是
在后面追加。
回滚段
1.设计Rollback Segment Header来存储各个undo页面链表的frist undo page的页号,这些页号被称为 undo slot
Rollback Segment Header的构成
1.每个页面对应一个段(一个段只有一个页面),这个段就是回滚段
2.TRX_RSEG_MAX_SIZE:本Rollback Segment中管理的所有Undo页面链表中的Undo页面数量之和的最大值,默认是无限大即
4个字节32位的最大值
3.TRX_RSEG_HISTORY_SIZE:History链表占用的页面数量。
4.TRX_RSEG_HISTORY:History链表的基节点
5.TRX_RSEG_FSEG_HEADER:本Rollback Segment对应的10字节大小的Segment Header结构,通过它可以找到本段对应的INODE Entry
6.TRX_RSEG_UNDO_SLOTS:各个Undo页面链表的first undo page的页号集合,也就是undo slot集合
从回滚段中申请Undo页面链表
1.初始情况下一个Rollback Segment Header的undo slot被设置成FIL_NULL(对应的十六进制就是0xFFFFFFFF),表示该undo slot不指向任何页面
2.开始有事务需要分配Undo页面链表了,即从回滚段第一个undo slot 开始,如果只是FIL_NULL则再表空间新创建一个
Undo Log segment,然后从段中申请一个页面作为undo页面链表的first undo page,然后把该undo slot值设置为刚刚申请的这个页面地址
这就完成了undo slot的分配
3.如果不是FIL_NULL,说明该undo slot已经指向了一个undo链表,也就是说这个undo slot已经被别的事务占用了,那就跳到下一个undo slot,判断该undo slot的值是不是FIL_NULL,重复上边的步骤
4.当一个Rollback Segment Header页面的1024个undo slot 都分配完毕,这个新事物无法再获取新的undo 页面链表,就会回滚这个事务并报错给用户
5.用户看到这个错误,可以选择重新执行这个事务(可能重新执行时有别的事务提交了,该事务就可以被分配Undo页面链表了)
6.当一个事务提交时,如果该undo slot指向的Undo页面链表符合被重用的条件,该undo slot就处于被缓存的状态
7.被缓存的undo slot都会被加入到一个链表,根据对应的Undo页面链表的类型不同,也会被加入到不同的链表:
8.如果对应的Undo页面链表是insert undo链表,则该undo slot会被加入insert undo cached链表。
9.如果对应的Undo页面链表是update undo链表,则该undo slot会被加入update undo cached链表。
10.如果有新事务要分配undo slot时,先从对应的cached链表中找。如果没有被缓存的undo slot,才会到回滚段的Rollback Segment Header页面中再去找。
11.如果该undo slot指向的Undo页面链表不符合被重用的条件,且对应的Undo页面链表是insert undo链表,则该Undo页面链表的TRX_UNDO_STATE属性会被设置为TRX_UNDO_TO_FREE,之后该Undo页面链表对应的段会被释放掉(也就意味着段中的页面可以被挪作他用),然后把该undo slot的值设置为FIL_NULL
12.如果该undo slot指向的Undo页面链表不符合被重用的条件,且对应的Undo页面链表是update undo链表,则该Undo页面链表的TRX_UNDO_STATE属性会被设置为TRX_UNDO_TO_PRUGE,则会将该undo slot的值设置为FIL_NULL,然后将本次事务写入的一组undo日志放到所谓的History链表中
多个回滚段
1.如果只有一个回滚段,表示最多只支持1024个事务并发,为了提高事务并发只能增加回滚段,目前有128个回滚段
2.每个回滚段都对应着一个Rollback Segment Header页面,有128个回滚段,自然就要有128个Rollback Segment Header页面
3.上述Rollback Segment Header页面 存在了innodb的系统表空间的第5号页面的某个区域,其包含了128个8个字节大小的格子
4.格子=Space ID+ page number
5.这里需要注意的一点事,要定位一个Rollback Segment Header还需要知道对应的表空间ID,这也就意味着不同的回滚段可能分布在不同的表空间中
回滚段的分类
1.第0号、第33~127号回滚段属于一类,第0号回滚段必须在系统表空间,第33~127号回滚段既可以在系统表空间中,也可以在自己配置的undo表空间中
2.如果一个事务在执行过程中由于对普通表的记录做了改动需要分配Undo页面链表时,必须从这一类的段中分配相应的undo slot。
3.第1~32号回滚段属于一类。这些回滚段必须在临时表空间(对应着数据目录中的ibtmp1文件)中。
4.如果一个事务在执行过程中由于对临时表的记录做了改动需要分配Undo页面链表时,必须从这一类的段中分配相应的undo slot
5.在修改针对普通表的回滚段中的Undo页面时,需要记录对应的redo日志,而修改针对临时表的回滚段中的Undo页面时,不需要记录对应的redo日志。
所以才把回滚段进行分类
为事务分配Undo页面链表详细过程
1.事务在执行过程中对普通表的记录首次做改动之前,首先会到系统表空间的第5号页面中分配一个回滚段(其实就是获取一个Rollback Segment Header页面的地址)
2.一旦某个回滚段被分配给了这个事务,那么之后该事务中再对普通表的记录做改动时,就不会重复分配了。
3.使用传说中的round-robin(循环使用)方式来分配回滚段。比如当前事务分配了第0号回滚段,那么下一个事务就要分配第33号回滚段,下下个事务就要分配第34号回滚段,简单一点的说就是这些回滚段被轮着分配给不同的事务
4.在分配到回滚段后,首先看一下这个回滚段的两个cached链表有没有已经缓存了的undo slot,比如如果事务做的是INSERT操作,就去回滚段对应的insert undo cached链表中看看有没有缓存的undo slot
5.如果事务做的是DELETE操作,就去回滚段对应的update undo cached链表中看看有没有缓存的undo slot。如果有缓存的undo slot,那么就把这个缓存的undo slot分配给该事务。
6.如果没有缓存的undo slot可供分配,那么就要到Rollback Segment Header页面中找一个可用的undo slot分配给当前事务。
7.找到可用的undo slot后,如果该undo slot是从cached链表中获取的,那么它对应的Undo Log Segment已经分配了,否则的话需要重新分配一个Undo Log Segment,然后从该Undo Log Segment中申请一个页面作为Undo页面链表的first undo page。
8.然后事务就可以把undo日志写入到上边申请的Undo页面链表了!
9.如果一个事务在执行过程中既对普通表的记录做了改动,又对临时表的记录做了改动,那么需要为这个记录分配2个回滚段。并发执行的不同事务其实也可以被分配相同的回滚段,只要分配不同的undo slot就可以了。
回滚段相关配置
配置回滚段数量
1.通过innodb_rollback_segments配置,但是范围只能是1到128
2.如果我们把innodb_rollback_segments的值设置为1,那么只会有1个针对普通表的可用回滚段,但是仍然有32个针对临时表的可用回滚段。
3.如果我们把innodb_rollback_segments的值设置为2~33之间的数,效果和将其设置为1是一样的
4.如果我们把innodb_rollback_segments设置为大于33的数,那么针对普通表的可用回滚段数量就是该值减去32。
配置undo表空间
1.第0号回滚段是一直在系统表空间的,但是第33~127号回滚段可以通过配置放到自定义的undo表空间中。但是这种配置只能在系统初始化(创建数据目录时)的时候使用,一旦初始化完成,之后就不能再次更改了
2.通过innodb_undo_directory指定undo表空间所在的目录,如果没有指定该参数,则默认undo表空间所在的目录就是数据目录。
3.通过innodb_undo_tablespaces定义undo表空间的数量。该参数的默认值为0,表明不创建任何undo表空间。
4.如果我们在系统初始化的时候指定了创建了undo表空间,那么系统表空间中的第0号回滚段将处于不可用状态。
5.设立undo表空间的一个好处就是在undo表空间中的文件大到一定程度时,可以自动的将该undo表空间截断(truncate)成一个小文件。而系统表空间的大小只能不断的增大,却不能截断。