209_mysql_innodb_5_Innodb_undo

举报
alexsully 发表于 2021/10/26 19:42:52 2021/10/26
【摘要】 undo 恢复流程

Undo 回滚

1 背景 回滚需求

每当对一条记录进行改动时(insertdelete, update)把回滚所需的东西记下来

Insert操作:至少记录该记录主键记下来, 回滚时 把这主键值对应的记录删掉

Delete 操作 至少记录原内容,回滚时 重新插入

Update操作 至少记录原内容,回滚时 更新为旧值

2 事物id

2.1 分配实际

 只读事物: 普通表无权修改,对临时表增删改时 分配事物id 

 读写事物:第一次对表执行增删改操作,会分配事物id ( 如果该事物里面全是select语句 也不会有事物id )

2.2 事物id 生成

  事物id 本质是一个数字,生成过程雷同row_id max row id

  Innodb维护全局变量 –> 分配给事物后自增1 –> 达到256倍数 –> 刷到系统表空间页号为5MAX TRX ID 属性中

2.3 trx_id 隐藏列

  行记录中的隐藏列之一(row_id, trx_id, roll_pointer) trx_id 对这个聚簇索引及记录进行改动的语句所在的事物(id

3 undo 日志的格式

一条记录进行一次改动记录一次undo(也可能两次undo日志), 一个事物可能包括多个undo日志, undo日志从0开始编号递增,编号=undo_no

3.1 insert操作对应的undo 日志

  回滚插入记录 -> 记录这条记录的主键信息,然后删除这条记录, innodb设计undo 日志类型为 TRX_UNDO_INSERT_REC

  1. Undo no 0开始递增,只要事物没提交, 每生成一个undo日志,undo no 就增1
  2. 如主键1列 就记录一列,联合主键要全部记录(len 代表列存储空间的大小, value 代表真实值) 例 <4 ,1> id为主键且int类型(占4字节, 真实记录 id =1,
  3. Roll_pointer 本质是个指针,指向记录对应的undo 日志

3.2 delete操作对应的undo日志

数据页中会根据每行记录的记录头信息中的delete & next_record 维护一个单向链表

  1. 正常数据记录链表 就是记录的next_record组成的链表
  2. 垃圾链表:被删除的记录 也会根据next_record组成链表,且数据页的pageheader中的 page_free属性会指向这个链表的头节点

删除一条记录的过程(delete mark & purge阶段)

  •  阶段1 delete mark阶段 仅仅将deleted_flag 标记为1, 事物提交前一直处于此阶段
  •  阶段2 purge 阶段, 语句提交后,后台线程来处理删除, 把记录从正常链表移除,加入到垃圾链表中,修改一些统计信息(PAGE_N_RECS, PAGE_LAST_INSERT)

事物提交不需要回滚,所以只针对阶段1 delete mark 阶段设计undo 日志(TRX_UNDO_DEL_MARK_REC) 结构如下

  1. Delete mark 前 记录trx_id roll_pointer的旧值,这样可以根据undo日志找到上一次对改记录进行修改的undo 日志
  2. 例 一个事物中 先插入 后删除操作:如下图 delete mark产生的日志 其实指向了 insert undo日志(版本链)
  3. 需要单独记录索引的信息(pos len value)  pos 是索引列的位置 (row_id, trx_id, roll_pointer)后开始算
  4. Len of index_col_info 就是索引+主键+自身占存储空间大小

3.3 update操作对应的undo日志 (更新主键和不更新主键两种情况)

 3.3.1 不更新主键

  1. a) 就地更新(in-place update) 被更新的列占存储空间不发生变化 (各个列在更新前和更新后占空间一致,一个列不同都不算就地更新)
  2. b) 先删除旧记录,再插入新记录 被更新的列占存储空间发生变化, 先删除该记录,再插入新记录(如果新插入记录占空间小于原始的 可以从垃圾链表里拿空间, 如果大于就新申请

针对不更新主键的undo日志类型 结构如下  N_updated: 本条update语句 将导致几个列被更新  <pos old_len old_value>  更新前的位置/大小/真实值

3.3.2 更新主键 (代表这条记录在聚簇索引中的位置将发生变化)

  如果将id 1的记录,改成id 100,且1~100 还有很多中间记录,需要2步完成更新

  • 将旧记录进行delete_mark操作 此时delete mark 操作是后台线程做purge操作,且把它加入垃圾链表, 记录在 TRX_UNDO_DEL_MARK_REC类型日志中

  • 根据更新后的数据 新插入一条记录在聚簇索引中,记录在 TRX_UNDO_INSERT_REC 类型中

3.4 增删改操作对二级索引的影响

   a)  对二级索引记录来说,insert/delete 操作同聚簇索引

    b)   如果update 更新列包括了二级索引列

         b1) 对旧的二级索引记录执行 delete_mark 操作

         b2) 根据更新后的值创建一条二级索引记录,在B+树根据位置插入

         b3) 此类更新会影响二级索引所在页page header中的 PAGE_MAX_TRX_ID属性(修改当前页的最大事物ID

4 通用链表结构

Undo 写入过程中会使用多个链表,此处描述下链表基本结构(基节点+链表通用结构)

5 FIL_PAGE_UNDO_LOG页面

页面类型为FIL_PAGE_UNDO_LOG 专门存储undo日志的数据页 (16KB) 结构如下

TRX_UNDO_PAGE_TYPE TRX_UNDO_INSERT & TRX_UNDO_UPDATE 仅能存这俩类型,且不能混用

TRX_UNDO_PAGE_START: 第一条undo日志的偏移量

TRX_UNDO_PAGE_FREE: 最后一条undo日志偏移量,这之后空间是free

TRX_UNDO_PAGE_NODE: 代表链表节点的结构

6 Undo页面链表

对普通表和临时表记录改动时参数的Undo日志分别记录, 附两个undo 页类型,一个事物最多可以有4个以undo页面为节点组成的链表, 每个链表的第一个节点为 first undo page

分配策略

  1. 刚开启事物 一个undo链表不分配
  2. 事物向普通表插入或更新主键记录 分配普通表的insert undo 链表
  3. 事物执行删除/更新普通表 分配 update undo 链表
  4. 事物执行临时表插入/更新主键 分配临时表 insert undo链表
  5. 事物执行 临时表 删除/更新 分配临时表 update undo 链表

7 Undo日志具体写入过程

7.1 segment header

一个索引会产生两个段,分别是叶子节点段和非叶子节点段,而每个段都会对应一个 INODE Entry 结构,我们要想知道某个段对应哪个INODE Entry 结构,就需要找个地方记下来这个对应关系 INDEX 类型的页里的 Page Header 部分; 知道了表空间, 页号, 页内偏移量可以定位到 INODE ENTRY

7.2 Undo Log Segment Header

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

在链表的第一个节点 first undo page 中,有一个 undo log segment header, 这个部分包含了改链表对应段的Segment Header信息

TRX_UNDO_STATE : Undo页面链表处于什么状态,

  •   TRX_UNDO_ACTIVE  活跃状态, 表示一个活跃事物在向这个页面链表中写入undo日志
  •   TRX_UNDO_CACHED  缓存状态 该页面的undo链表等待被重用
  • TRX_UNDO_TO_FREE   等待被释放状态,对于insert undo 链表,如果事物提交,该链表不能被重用
  • TRX_UNDO_TO_PURGE  purge状态, 对于update undo链表,如果事物提交,该链表不能被重用
  • TRX_UNDO_PREPARED 此状态表示该链表用于存储处于prepare阶段事物产生的日志

TRX_UNDO_LAST_LOG undo页面链表中最后一个Undo Log Header的位置

TRX_UNDO_FSEG_HEADER: undo页面链表对应的段的Segment Header信息, 通过这个可以找到对应的INODE Entry

TRX_UNDO_PAGE_LIST: undo 页面链表的基节点(每个undo页面都有TRX_UNDO_PAGE_NODE存链表

7.3 Undo Log Header

Innodb 规定,同一个事物向一个undo页面链表中写入undo 日志算一组,每写一组undo日志,都会记录关于这个组的信息(存在Undo Log Header

 所有undo页面链表的第一个页,在写入undo 日志前,都会被填充 Undo Page Header, Undo Log Segment Header, Undo Log Header

TRX_UNDO_TRX_ID (8)  生成本组undo 日志的事物ID 
TRX_UNDO_TRX_NO (8) 事物提交后生成一个序号,此序号来标记事物提交顺序(先提交号小)
TRX_UNDO_DEL_MARKS (2) 本组undo日志中是否包括 delete mark 操作产生的undo日志
TRX_UNDO_LOG_START(2)  本组 undo日志中第一条undo日志在页面中偏移量
TRX_UNDO_XID_EXISTS(1)   本组undo 日志是否包括XID信息
TRX_UNDO_DICT_TRANS(1) 本组日志 是不是由DDL语句产生
TRX_UNDO_TABLE_ID(8)  如果TRX_UNDO_DICT_TRANS为真,DDL对应表ID
TRX_UNDO_NEXT_LOG(2) 下一组undo 日志在页面中偏移量
TRX_UNDO_PREV_LOG(2) 上一组undo 日志在页面中偏移量
TRX_UNDO_HISTORY_NODE(12)  链表history 链表
XID信息 (140)

注解:正常一个undo页面链表只存储一个事物执行过程中产生的undo日志,但是会有重复利用情况,一个事物提交后,后续事物重复使用这个链表,TRX_UNDO_NEXT_LOG(2) RX_UNDO_PREV_LOG(2) 用来标记 不同组在页面中的偏移量

8 重用Undo页面

  为了提高并发执行多个事物写入undo日志性能,innodb会给每个事物单独分配undo 页面链表(最多单独分配4个链表), 为了避免浪费资源, innodb决定重复利用Undo页面的链表,但要满足下面条件

  1. 该链表中只包含一个undo页面,该链表才能被下一个事物重用
  2. undo页面使用空间小于当前页面的3/4

 8.1 insert undo链表和update undo链表差异

        insert undo 链表: 只存储TRX_UNDO_INSERT_RECundo日志,A事物提交后(只有一个页面/或小于3/4空间) B 事物可以把A事物一组就的Undo覆盖重用

    undate undo 链表: 一个事物提交后,她的update undo 链表不能立即删除/覆盖(用于MVCC),所以同一个undo页面写入了多组undo 日志(属于不同事物)

9 回滚段 Rollback Segment

 9.1 概念

   一个事物执行时候,最多可以分配4undo页面链表,系统里有很多undo页链表,为了维护,innodb 定义Rollback Segment Header的页面,存各个undo链表的 first undo page 页号(undo slot

  每个Rollback Segment Header 页面都对应一个段(Rollback Segment)且这个段只要一个页

TRX_RSEG_MAX_SIZE : 是个阈值,undo页面链表中的undo页面数量之和不能超过这个值,默认无限大

TRX_RSEG_HISTORY_SIZE : history 链表占用的页面数量

TRX_RSEG_HISTORY: history 链表的基节点

TRX_RSEG_FSEG_HEADER:  这个回滚段对应的10字节大小的segment Header结构,通过它可以找到Innode Entry

TRX_RSEG_UNDO_SLOTS:  各个undo页面链表的fist undo page的页面集合(undo slot集合 一个页号4个字节, TRX_RSEG_UNDO_SLOTS能存1024个,所以占4096个字节)

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

  a) 初始化后,未向任何事物分配undo链表, 对于rollback segment header,它各个undo slot 都被赋予 FIL_NULL 
  b)  开始给事物分配 undo页链表. 从回滚段的第一个undo slot 开始
  c)  如果是 fil_null, 就在表空间中创建一个段(undo log segment) , 然后从中申请一个页 作为undo链表的 first node page , 最后把该页的地址赋值给 undo slot (意味着这个slot 分配给了事物)
  d) 如果不是 fil_null  说明该slot 已经给了一个事物了,需要重新指定(下一个判断,以此类推) 如果1024个都用完了,报错:too many active conrrent transactions

当事物提交后,undo slot 两种命运

 1 如果该undo slot 指向的undo页面链表被重用,该undo slot 就处于被缓存的状态(TRX_UNDO_CACHED),一个回滚段对应下面俩cached链表, 如果新事物要分配undo slot 就从下面找

  •    1.1  如果该 undo slot 页面链表是insert undo, undo slot 会被加入 insert undo cached链表中
  •    1.2如果该 undo slot 页面链表是update undo, undo slot 会被加入 update undo cached链表中

2 如果该undo slot 指向undo 页面不符合重用条件,会有不同处理方式

  •   2.1 insert undo 链表,则undo 页面链表的TRX_UNDO_STATE 属性会被设置为 TRX_UNDO_TO_FREE,undo页面链表对应的段会被释放掉,然后把该undo slot 值设置为FIL_NULL
  •   2.2 update undo链表  则undo 页面链表的TRX_UNDO_STATE 属性会被设置为 TRX_UNDO_TO_PRUGE, 然后把该undo slot 值设置为FIL_NULL,不做释放,将本次事物写入一组undo日志 放到History链表中

9.3 多个回滚段

   一个回滚段最多1024 undo slot, 也就是最多1024个事物执行,肯定不够,后期定义了128个回滚段,128*1024 = 131072 undo slot

   每个回滚段 对应一个 rollback segment header页面, 128 rollback segment header 页需要存储,innodb 在系统表空间 第5号页面规划了了1288字节大小的格子, 8个字节由两部分组成

  • 4 字节的 spaceid 代表表空间id
  • 4 字节的 page number 代表页号

9.4 回滚段分类 128个回滚段,0 ~127编号

  • 0号,33-127号回滚段属于一类, 其中0号属于系统表空间,33-127既可以系统表空间,也可以用户配置的空间
  • 1 – 32号属于一类, 这些必须在临时表空间(ibtmp1)

9.5 roll_pointer的组成(本质是个指针)

Is_insert 表示该指针指向的undo日志是否为 TRX_UNDO_INSERT类型

Rseg id   表示该指针指向的undo日志的回滚段编号

Page number 表示该指针指向的undo日志所在的页面编号

Offset 表示该指针指向的undo日志在页面中的偏移量

10 回滚段相关配置

10.1 配置回滚段的数量

  系统中默认128show VARIABLES like "%innodb_rollback_segments%"

 如果设置成1 ,就只有一个针对普通表的回滚段,对临时表有32个
 如果设置成2 – 33 之间, 效果同 1 
 如果设置成 大于 33的, 例如 50, 针对普通表的回滚段就是 50- 32 = 18

 

 10.2 配置undo表空间

show VARIABLES like "%innodb_undo_directory%"  # 目录
show VARIABLES like "%innodb_undo_tablespaces%" #打开独立undo模式,并 设置undo的个数

show VARIABLES like "%innodb_max_undo_log_size%"  #undo日志的大小,默 认1G

show VARIABLES like "%innodb_undo_log_truncate%"  #开启undo自动回收的 机制(undo_purge)。

show VARIABLES like "%innodb_purge_rseg_truncate_frequency%" #触发自动回收的条件, 单位是检测次数。

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

系统崩溃时候,未提交的事物 必须回滚掉,如何回滚呢?

  • 先找到 128个回滚段,每个回滚段1024 undo slot , 找到类型不是 fill_null slot , 每个slot 对应一个undo页面链表
  • undo页面链表的第一个页面 找到 undo segment header 找到 TRX_UNDO_STATE属性(标志该链表所处状态)
  • 如果是 TRX_UNDO_ACTIVE 活跃的, 然后在undo segment header中找到 TRX_UNDO_LAST_LOG属性,通过该属性找到本undo 页链表最后一个UNDO LOG Header位置
  • 从该undo log Header 中找到对应事物的ID 及其其他信息,该事物ID 对应的事物就是未提交的事物
  • 通过Undo日志中记录的信息将该事物对页面的更改回滚即可

多个链表的源数据信息(只取first page node的页号 undo slots)成为 rollback segment header 

Undo 多个undo页链表 每个链表属于 undo log segment 段, 从段中申请具体的页(first page  normal page

First page (undo log segment header  & undo log header )

undo log segment header 本意是只向某个段,但是其他属性:TRX_UNDO_STATE 状态TRX_UNDO_LAST_LOG(UNDO LOG HEADER); TRX_UNDO_PAGE_LIST(页面链表的基节点)

undo log header ( 同个TRXundo 算一组,该组的metadata – undo log header, 核心结构: TRX_ID, trx_undo_log_start 本组undo中第一条undo在页面中偏移量 )

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。