什么是undo log

事务执行过程中会遇到一些突然状况,导致事务无法正常结束:

  1. 服务器错误、操作系统错误、突然断点等不可抗因素
  2. 事务执行过程中,通过rollback指令回滚

这种执行一半的事务,可能已经修改了很多数据,根据事务的原子性要求,没有完成的事务必须回滚到执行前的状态,看起来像什么事都没发生过的样子,因此回滚比较重要,回滚的实现方式大致如下:

  1. insert: 插入一条记录时,将这条记录的主键记录下来,回滚时根据这个主键删除即可。
  2. delete: 删除一条记录时,将这条记录的内容记录下来,回滚时重新插入到表中即可。
  3. update:修改一条记录时,将被更新的列的旧值记录下来,回滚时将这些值更新回去即可。

MySQL将这种用于回滚的记录称为 undo log,查询操作并不会对记录造成影响,因此没有对应的undo log。

事务id

事务分为两种,只读事务和读写事务

  1. 通过 start transaction read only 开启的事务是一个只读事务,只读事务中不可以对普通的表进行增删改,但可以对临时表做这些操作。

  2. start transaction read write 开启一个读写事务,读写事务中可以对表进行增删改查操作。

在事务的执行过程中,第一次对表进行增删改操作会分配一个事务id,如果是只读事务,则是第一次对临时表进行增删改时分配,如果事务中并没有写操作,则不会分配事务id。

事务id是怎么生成的

  1. MySQL内部定义了一个全局变量,每当有事务需要id时,则将当前的值分配给该事务,并进行自增1

  2. 当这个变量值为256的倍数时,则刷新到系统表空间中页号为5的页面下的一个名为max trx id的属性中,该属性占用8字节的空间

  3. 当系统下次启动时,会将该值加载到内存中并再加上256(防止上次关闭时内存中的值没有及时刷到盘中)

trx_id隐藏列

在Innodb行格式中,有一个trx_id的隐藏列,该列用于表示对当前记录进行写操作的事务id。

undo日志的格式

undo日志被记录在类型为 fil_page_undo_log 的页面中,这些页面从系统表空间或undo tablespace中分配,不同的操作有不同的日志类型。

roll_pointer

行格式中的roll_pointer字段是一个指针,用于指向当前记录回滚时的undo log,假设新增两行数据,那么这两行数据将分别指向对应的 trx_undo_insert_rec 类型的 undo log,当回滚时根据两个log对新增行做删除即可。

insert日志类型 (trx_undo_insert_rec)

一条 insert undo log 的格式如下:

  1. end of record:下一条undo日志的位置
  2. undo type:日志类型,即insert
  3. undo no:日志编号,一个事务中,日志编号从0开始,事务中的每个日志都会递增+1
  4. table id:日志所属的表id
  5. 主键各类信息:记录主键的长度和值,是一个列表,因为主键可能是多个键组成的,len代表值的长度,value代表值的内容
  6. start of record:上一条undo日志的位置

在insert一条数据时,会向聚簇索引、二级索引都插入一条记录,但是undo日志只会记录一条针对聚簇索引的日志,后面回滚的时候使用聚簇索引进行回滚删除会将其他索引也一起删除,后面的delete、update类型也是一样。

delete日志类型 (trx_undo_del_mark_rec)

一条 delete undo log 的格式如下:

  1. end of record:下一条undo日志的位置
  2. undo type:日志类型,即delete
  3. undo no:日志编号,一个事务中,日志编号从0开始,事务中的每个日志都会递增+1
  4. table id:日志所属的表id
  5. info bits:记录头信息的前4个bit值
  6. trx_id:旧记录的trx_id值
  7. roll_pointer:旧记录的roll_pointer值
  8. 主键各列信息:记录主键的长度和值,是一个列表,因为主键可能是多个键组成的,len代表值的长度,value代表值的内容
  9. len of index_col_info:索引列各列信息的总大小
  10. 索引列各列信息:所有索引列的信息
  11. start of record:上一条undo日志的位置

和insert不同,多出了一个 索引列各列信息 的内容,这部分信息主要在事务提交后使用,用于对中间状态的记录进行真正的删除。

数据删除的过程(purge)

数据页中有两个链表,一个是正常记录链表,还有一个是被标记为删除的数据链表,称为垃圾链表,垃圾链表的头节点被Page Header中的 page_free 指向。

删除一条数据需要两个阶段:

  1. 将记录的deleted_flag设置为1,这个阶段被称为delete mark,此时这条记录不会被加入到垃圾链表中,而是处于被标记为删除的中间状态。

  2. 当事务提交后,该记录将会由专门的线程从正常记录链表中进行移除,加入到垃圾链表中,随后调整页面的其他信息,page_n_recs、page_last_insert、page_garbage 以及页目录等信息,这个阶段被称为 purge。

被删除的记录加入到垃圾链表的位置是头节点,此时的page_free也会指向他,之前的头节点被当前节点的next指向,

数据页Page Header中有一个page_garbage属性,用于表示当前页面中可重用存储空间的总字节数,每次有已删除记录被加入垃圾链表后,该值都会加上已删除记录占用的存储空间大小,

每当有新记录要插入这个页面的时候,首先判断垃圾链表的头节点指向的已删除记录的存储空间是否可以容纳这条新记录,如果不可以容纳,就直接向页面申请新的空间来存储这条记录,

如果可以容纳,则重用这条已删除记录的存储空间,并让page_free指向垃圾链表中的下一条已删除记录,如果新插入的记录大小小于垃圾链表头节点的存储空间,将产生碎片空间,

这些碎片空间会被统计在page_garbage中,等到页面空间不足以再分配一条完整记录的空间时,会查看page_garbage和剩余空间相加之后是否可以容纳这条新记录,

如果可以,Innodb会尝试重新组织页内的记录,即先开辟一个临时页面,将页面内的记录依次插入一遍,然后再将临时页面的内容复制回来,即可利用碎片空间容纳新记录。

update日志类型

执行update语句时,主键是否被更新,Innodb要做的处理也不相同。

1.不更新主键

就地更新

如果被更新的列在更新前和更新后占用的空间是一样的大小,那么记录就可以原地更新,注意大小必须一致,大于或小于都不会原地更新。

先删除旧记录,再插入新记录

如果大小不一致,则会将原本的记录进行删除操作,然后再插入新记录,不过这个删除是用户线程同步删除,而不是使用purge操作,

同步删除时同时更新页面的相关统计信息(page_free、page_garbage等),随后插入新记录时,如果新记录的大小不超过旧记录的大小,可直接使用垃圾链表中旧记录的存储空间,否则需要向页面申请一个新空间,如果页面内没有可用的空间,将进行分裂操作,再插入新记录。

不更新主键的update undo log类型为 trx_undo_upd_exist_rec , 与delete类型很相似,不过多出了两个属性:

  1. n_updated:共有多少列被更新
  2. 被更新的列更新前信息:pos代表被更新列在记录中的位置,old_len和old_value分别代表值占用的空间和列真实值

如果在update语句中更新的列包含索引列,那么将会添加索引列割裂信息这个部分,否则不会添加。

2. 更新主键

如果更新了主键,意味着该记录在聚簇索引中的位置将发生改变,极有可能不在同一个页面中,更新记录的主键分为两步操作:

  1. 将旧记录进行delete mark操作

这里是delete mark,也就是说后续提交的时候要进行purge操作,而不是上面的不更新主键的用户线程同步删除。

  1. 根据新的主键,将该记录进行一次insert操作,放到所属的页面中

针对更新主键这种情况,会产生两条日志,一个类型为 trx_undo_del_mark_rec 另一个则是 trx_undo_insert_rec,即这种情况将删除和插入两种组合起来了。

增删改查对二级索引的影响

对于二级索引,insert操作和delete操作与在聚簇索引中执行产生的影响类似,但是update略有不同,如果SQL更新了二级索引的值那么需要有下面两步操作:

  1. 将旧的二级索引执行delete mark操作。
  2. 创建一条新的二级索引记录在B+树中插入进去。

二级索引是没有trx_id、roll_pointer这类属性的,不过当增删改一条二级索引记录时,会影响其所在页面的Page Header中的 page_max_trx_id 属性,这个属性代表修改当前页的最大的事务id。

通用链表结构

在写入undo log的过程中,会用到多个链表,这些链表基本都有同样的节点结构,共12字节:

  1. 指向上一个节点的指针

Prev Node Page Number(4字节)、Prev Node Offset(2字节),这两个组合成为指向上一个节点指针

  1. 指向下一个节点的指针

Next Node Page Number(4字节)、Next Node Offset(2字节),这两个组合成为指向下一个节点指针

基节点

基节点用于指向链表的头节点和尾结点,以及链表长度信息,共占用16字节:

  1. List Length(4字节)

    表示链表一共有多少节点

  2. First Node Page Number(4字节)、First Node Offset(2字节)

    指向链表头节点的指针

  3. First Node Page Number(4字节)、First Node Offset(2字节)

    指向链表尾节点的指针

fil_page_undo_log 页面

不同的数据存在不同的页面中,B+树索引存在 fil_page_index 类型的页面中,表空间头部信息存储在 fil_page_type_fsp_hdr 类型的页面中,undo 日志存储在类型为 fil_page_undo_log 的页面中。

undo页面结构分为4部分:

  1. File Header:页面通用结构
  2. Undo Page Header
  3. body:存放真正的undo日志
  4. File Trailer:页面通用结构

Undo Page Header 是undo页面独有的结构,其结构如下:

  1. trx_undo_page_type:本页面准备存储什么类型的undo日志

    undo日志类型被分为两种,第一种是 trx_undo_insert(十进制的1表示),产生的插入日志都属于这种类型,也称为insert undo日志,

    第二种是 trx_undo_update(十进制的2表示),除了insert类型,其他都属于这种类型,统称update undo日志。

    一个页面只能存储一种类型的undo日志,不可以混合存储,之所以做出区分,是因为insert类型的日志可以在事务提交后直接删除,而update类型的由于需要为MVCC服务,因此要区别对待。

  2. trx_undo_page_start:第一条undo日志在本页面中的起始偏移量

  3. trx_undo_page_free:最后一条undo日志结束时偏移量

  4. trx_undo_page_node:链表节点结构,上面提到的

undo页面链表

1. 单个事务中的undo页面链表

一个事务可能有多个SQL语句,一个语句可能对若干个记录进行改动,对每条记录改动前(聚簇索引记录),都需要记录1-2条undo日志,所以一个事务可能产生很多日志,

这些日志在一个页面中可能放不下,那么就需要放到更多的页面中,这些页面就通过 trx_undo_page_node 形成了一个链表,

链表中的第一个undo页面称为first undo page,其余称为 normal undo page,因为第一个页面除了 undo page header 还有一些其他的管理信息。

一个事务的执行过程中,增删改的操作都会有,因为一个undo页面只能存放一种类型,所以一个事务的执行过程中可能有两种链表,一个是insert undo链表,一个是 update undo链表,

此外,Innodb还规定,普通表和临时表的undo日志也要分别记录,所以一个事务中如果同时对临时表,普通表进行增删改操作,就会有4个链表。

链表分配策略为按需分配,分配策略如下:

  1. 刚开启事务时,一个链表也不分配
  2. 当对普通表执行插入操作或更新主键操作,将分配一个普通表的insert undo链表
  3. 当对普通表执行删除操作或更新操作,将分配一个普通表的update undo链表
  4. 当对临时表执行插入操作或更新主键操作,将分配一个临时表的insert undo链表
  5. 当对临时表执行删除操作或更新操作,将分配一个临时表的update undo链表

2. 多个事务中的undo页面链表

当有多个事务发起增删改的操作时,为了提高undo日志的写入效率,不同的事务要将日志写到不同的undo页面链表中,因此事务越多,undo页面链表越多。

undo日志的写入过程

1. 段的概念

一个B+树会被分为两个段,一个叶子节点段和一个非叶子节点段,这样叶子节点就会被尽可能的存放在一起,非叶子节点就会尽可能的存在一起,

每一个段对应一个INODE Entry结构,该结构描述了段的具体信息,例如段的ID、段内的各种链表基节点,零散页面的页号有哪些,

Innodb设计了一个Segment Header结构,可以定位到一个INODE Entry,结构如下:

  1. Space ID of the INODE Entry:INODE Entry结构所在的表空间ID
  2. Page Number of the INODE Entry:INODE Entry结构所在的页面页号
  3. Byte Offset of the INODE Entry:INODE Entry结构在该页面中的偏移量

2. Undo Log Segment Header

每个Undo页面链表都对应着一个段,称为 Undo Log Segment,链表中的页面都是从该段中申请的,

所以Undo页面链表的第一个页面中设计了一个 Undo Log Segment Header 部分,这个部分包含了链表对应的短的 Segment Header信息,以及其他一些关于这个段的信息。

Undo Log Segment Header的结构如下:

  1. trx_undo_state

    本undo页面链表处于什么状态,可能的状态有下面几种:

    • trx_undo_active:活跃状态,即一个活跃的事务正在向这个Undo页面链表中写入Undo日志。
    • trx_undo_cached:被缓存状态,该状态的Undo页面链表等待被其他事务重用。
    • trx_undo_to_free:等到被释放的状态,对于insert undo类型,在其对应的事务提交后,该链表不会被重用,就是这种状态
    • trx_undo_purge:等待被purge的状态,对于update undo类型,如果在其对应的事物提交后,该链表不能被重用,则处于这种状态
    • trx_undo_prepared:此状态用于存储处于prepare阶段事务产生的日志
  2. trx_undo_last_log:本Undo页面链表中最后一个Undo Log Header的位置

  3. trx_undo_fseg_header:本Undo页面链表对应的段的Segment Header信息

  4. trx_undo_page_list:Undo页面链表的基节点,用于串联起其他页面的 trx_undo_page_node 属性,形成一个链表

3. Undo Log Header

一个事务在向Undo页面中写入日志,就是不停的填充,一条写完写下一条,一个页面写完后,申请一个新页面继续写,然后将新页面加入到Undo页面链表中,

同一个事务想一个Undo页面链表中写入的undo日志算是一个组,例如一个事务需要分配3个Undo页面链表,那就是3个组的日志,

每写一组日志时,都会在这组日志前先记录一下这个关于组的一些属性,这些属性被存在第一个页面的Undo Log Header属性中,该属性结构如下:

  1. trx_undo_trx_id:生成本组undo日志的事物id

  2. trx_undo_trx_no:事务提交后生成的序号,此序号用来标记事务的提交顺序(先提交的序号小,后提交的序号大)

  3. trx_undo_del_marks:标记本组日志中是否包含由delete mark操作产生的undo日志

  4. trx_undo_log_start:表示本组日志中第一条undo日志在页面中的偏移量

  5. trx_undo_xid_exists:本组日志是否包含XID信息

  6. trx_undo_dict_trans:标记本组undo日志是不是由DDL语句产生的

  7. trx_undo_table_id:如果 trx_undo_dict_trans 为真,本属性表示DDL操作的表id

  8. trx_undo_next_log:下一组日志在页面中开始的偏移量

  9. trx_undo_prev_log:上一组日志在页面中开始的偏移量

    一般一个Undo页面链表只存储一个事务执行过程中产生的一组undo日志,不过某些情况下,一个事务提交后,后续开启的事务又重复利用这个页面链表,

    这样一个页面可能存放多组undo日志,trx_undo_next_log 、prev_log 用于标记上一组和下一组日志在页面中的偏移量。

  10. trx_undo_history_node:一个12字节的链表节点结构,表示名为history链表的节点

重用Undo页面

为了提高并发执行多个事务写入undo日志的性能,会为每个事务单独分配相应的undo页面链表,但是如果绝大多数事务仅仅修改了一条或几条记录,

那么针对某个undo页面链表只产生了非常少的undo日志,只占用很小的一部分空间,页面中的其他空间都浪费了,因此在满足下面两种情况下,undo页面链表可以被重用:

  1. 该链表中只包含一个undo页面

    如果一个事务在执行过程中产生了很多的undo日志,那么可能申请了很多的页面加入链表中,该事务提交后,如果将整个链表的页面都重用,就意味着新事务即使不需要写很多日志,也需要维护一个庞大的链表,

    那些用不到的页面也不可以继续被其他的新事务使用,这两方面造成了性能和资源浪费,因此链表中只包含一个undo页面才可以被重用。

  2. undo页面已使用空间仅占用整个页面空间的3/4

    如果页面空间已经被使用的所剩不多,那么重用也无法得到好处。

重用策略

insert undo链表与update undo链表在重用时,策略也是不同的:

  1. insert undo链表

    insert类型的undo日志在事务提交后就没用了,可以直接清除,所以在满足上面提到的两种情况下,新的事务可以直接将该链表中的页面直接覆盖使用,

    覆盖写入时,不仅会写入新的 Undo Log Header,还会适当调整 Undo Page Header、Undo Log Segment Header、Undo Log Header中的一些属性。

  2. update undo链表

    在满足上面提到的两种条件,由于update类型的日志要用于MVCC,所以不可以覆盖写入,而是追加写入,此时这个页面中就有了多组undo日志。

回滚段

1. 什么是回滚段

一个事务执行过程中可以分配4个Undo页面链表,同一时刻,不同事务拥有的Undo页面链表不同,整个系统中同一时刻存在很多个Undo页面链表,

为了方便管理这些Undo页面链表,Innodb中设计了一个 Rollback Segment Header 的页面,这个页面中存放了各个Undo页面链表的first undo page的页号,这些页号被称为 undo slot,通过这些页号就可以方面的管理各个链表了。

每个 Rollback Segment Header 都对应一个回滚段 Rollback Segment,回滚段中只存在一个页面。

Rollback Segment Header结构如下:

  1. trx_rseg_max_size(4字节):表示回滚段中所有的Undo页面链表中的Undo页面数量之和的最大值,表示最多能创建多少个undo页面。

  2. trx_rseg_history_size(4字节):history链表占用的页面数量

  3. trx_rseg_history(16字节):history链表的基节点

  4. trx_rseg_fseg_header(10字节):回滚段对应的Segment Header结构,通过它可以找到本回滚段对应的INODE Entry

  5. trx_rseg_undo_slots(4096字节):各个页面链表的first undo page的页号集合,即undo slot集合

    一个页号占用4字节,trx_rseg_undo_slots可以存储1024个页号。

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

初始情况下,没有事务申请undo页面链表,因此 Rollback Segment Header页面中的页号值都是 FIL_NULL(十六进制的0xFFFFFFFF),这表示页号不指向任何页面,

事务开始申请undo页面链表时,从回滚段的第一个页号开始查看该值是否是 FIL_NULL:

  1. 如果是 FIL_NULL

    在表空间中创建一个新的段(Undo Log Segment),然后从段中申请一个页面作为undo页面链表的 first undo page,最后将该页面的地址赋值给 undo slot。

  2. 如果不是 FIL_NULL

    undo slot已经指向了一个链表,即已经被其他事务占用,重复上一步,如果1024个slot都不是FIL_NULL,那么新事务无法获得新的Undo页面链表,则会停止这个事务,并且向用户报错:

    Too many active concurrent transactions

    用户可以选择放弃,或者重试。

当事务提交时,undo slot有两种状况:

  1. 如果undo slot指向的链表符合被重用的条件,则状态变为被缓存的状态,页面链表的trx_undo_state属性会被设置为 trx_undo_cached

    被缓存的undo slot会加入到一个链表中,不同的undo页面链表对应的undo slot会加入到不同的链表中

    • insert undo 链表,加入到insert undo cached链表中
    • update undo 链表,加入到update undo cached链表中

    一个回滚段对应上述两个cached链表,如果有新事务要分配undo slot,会先在cached链表中找,找不到再去回滚段中寻找。

  2. 如果不符合重用的条件,根据不同的链表类型,处理也不同

    • 如果是insert undo链表,则该页面链表的 trx_undo_state属性被设置为 trx_undo_to_free,之后该页面链表对应的段会被释放,然后将该undo slot的值设置为FIL_NULL

    • 如果是update undo链表,则页面链表的 trx_undo_state属性被设置为 trx_undo_to_purge,并且undo slot的值设置为FIL_NULL,然后将本次事务写入的一组undo日志放到History链表中,但是Undo页面链表对应的段不会释放,需要给MVCC使用。

3. 多个回滚段

一个事务在执行过程中最多分配4个undo页面链表,一个回滚段中只有1024个undo slot,意味着同时只支持1024个事务的并发,

为了支持更多的事务执行,Innodb定义了128个回滚段,因此可以支持更多的事务,这些回滚段的页面存在系统表空间的第五个页面的一个区域中,

该区域中包含了128个8字节大小的格子,8字节的组成:

  1. 4字节的Space Id:代表一个表空间的id
  2. 4字节的Page number:代表一个页号

这8个字节组成一个指针,指向某个表空间的某个页面,该页面就是Rollback Segment Header页面,不同的回滚段可能分配在不同的表空间中。

4. 回滚段的分类

128个回滚段,每个段都有自己的编号,从0到127,共分成两大类:

  1. 0、33~127

    第0号、33127号属于一类,第0号必须分配在系统表空间中,33127可以在系统表空间,也可以在用户配置的undo表空间中。

    如果一个事务在执行过程中对普通标的记录进行了改动,就必须从这个类的段中分配相应的undo slot。

  2. 1~32

    1~32属于一类,这类回滚段必须在临时表空间中,对应数据目录中的ibtmp1文件,如果一个事务在执行过程中对临时表记录做了改动,页面链表将从这里分配相应的undo slot。

之所以对回滚段进行分类的原因是,我们在运行过程中对每一个页面做出修改时都需要记录redo日志,方便系统崩溃重启时恢复,

undo日志的写入也是写到页面中,因此写undo日志也需要记录redo日志,但是对于临时表的操作,仅仅是在系统运行中的,重启后我们并不需要恢复这些临时表,

因此将临时表的undo日志和普通表的undo日志进行分开,可以判别对应的undo段需不需要写redo日志,如果是1~32就不需要写redo,其他的则需要。

5. roll_pointer的组成

聚簇索引记录中包含一个名为roll_pointer的隐藏列,有些undo日志包含一个名为roll_pinter的指针,指向一条undo日志的地址,roll_pointer由7个字节组成,共包含4个属性:

  1. is_insert:表示该指针指向的undo日志是否是trx_undo_insert类的undo日志
  2. rseg id:表示该指针指向的undo日志的回滚段编号
  3. page number:该指针指向的undo日志所在页面的页号
  4. offset:该指针指向的undo日志在叶面中的偏移量

6. 为事务分配Undo页面链表的详细过程

共有5步

  1. 事务首次修改普通表的记录时,先去系统表空间的5号页面中分配到一个回滚段,之后该事务再修改记录时,不会重复分配,多个回滚段的分配方式使用 round-robin 来分配,从第一大类中循环分配回滚段给多个事务。

  2. 分配到回滚段后,查看回滚段的两个cached链表是否有缓存的undo slot,不同的操作看不同的链表,insert类的看insert undo cached,update类型的看 update undo cached。

  3. 如果在缓存中没找到,就从回滚段中分配一个可用的undo slot。

  4. 找到可用的undo slot,如果该slot是从缓存链表中获取的,其Undo Log Segment已经分配,否则就需要重新分配一个Undo Log Segment,然后从该 Segment 中申请一个页面作为Undo页面链表的 first undo page,并把该页填入undo slot中。

  5. 事务开始写入日志到Undo页面链表中。

临时表也是类似。

回滚段的相关配置

1. 配置回滚段数量

128个回滚段是系统默认值,该值可以做修改,通过启动选项 innodb_rollback_segments 配置,配置范围是1~128,但是该值仅针对普通表有效,临时表不受这个影响。

  1. 如果值为1,针对普通表只有1个回滚段,临时表依然是32个

  2. 如果值是2~33之间的数字,效果与上面一样

  3. 如果值是大于33的数组,普通表可用回滚段为该值-32

2. 配置undo表空间

0号段必须在系统表空间中,其他段可以通过配置放到自定义的undo表空间中,不过这个配置只能在系统初始化创建数据目录时使用,之后不可更改,启动选项如下 :

  1. innodb_undo_directory:指定undo表空间所在的目录,如果没有指定,默认undo表空间所在的目录就是数据目录

  2. innodb_undo_tablespaces:定义undo表空间的数据量,默认为0,即不创建任何undo表空间,如果在系统初始化时创建了undo表空间,那么系统表空间中的0号回滚段将处于不可用状态

33~127回滚段可以均匀分配到不同的undo表空间中,比如在系统初始化时指定 innodb_rollback_segments为35,innodb_undo_tablespaces为2,那么33、34号回滚段将分配到不同的undo表空间中,

设立表空间的好处在于表空间中的文件大到一定程度时,可以自动将该表空间截断成一个小文件,而系统表空间只能不断增大,不能截断。

undo日志在崩溃恢复时的作用

在系统崩溃重启后,redo日志会将各个页面中的数据恢复到崩溃前的状态,但是带来一个问题,没有提交的事务写的redo日志可能也刷盘了,那么未提交的事务修改的页面也可能被恢复,

为了保证事务的原子性,这些未提交的事务必须回滚到之前的样子,回滚就需要通过undo日志来做,通过系统表空间5号页面找定位到128个回滚段的位置,

在每个回滚段中的undo slot中找到不为FIL_NULL的slot,然后找到undo页面链表,如果链表的trx_undo_state属性是trx_undo_active,则意味着有一个活跃的事务向该链表中写入日志,

再通过 Undo Segment Header中找到trx_undo_last_log属性,通过该属性找到链表中最后一个undo log header的位置,从header中找到对应事务的id以及其他信息,该事务就是未提交事务,

通过undo日志中记录的信息将该事务对页面所做的更改全部回滚掉,保证原子性。


作者:🐳🐳🐳阿布
链接:https://juejin.cn/post/7084623868094218270/
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐