目录
- 一、前言
- 1.1 如何回滚事务
- 1.2 事务id
- 1.3 roll pointer 隐藏列
- 1.4 trx_id 隐藏列
- 二、undo日志
- 2.1 undo日志的格式
- 2.2 insert 对应的undo日志
- 2.3 delete 操作对应的undo日志
- 2.4 update操作对应的undo日志
- 2.5 Undo页面链表
- 2.6 undo日志写入过程
- 2.6.1 Undo Log Header
- 2.7 回滚段
- 2.7.1 回滚段的概念
一、前言
1.1 如何回滚事务
原子性是事务的其中一个特性,指的是要么全部执行完,要么全都不执行,说的容易,如何保证这一特性呢?
事务在执行的过程中难免会发生某些意外:
- 服务器错误、操作系统错误、电脑直接掉电、、、
- 有时程序员也想手动的回滚事务
上述这些情况,都会导致事务执行一般就结束,但是事务在执行过程中已经修改了很多数据,为了保证事务的原子性,我们必须改回原来的样子。
数据库的回滚操作和悔棋类似,如果向数据库中插入了一条数据,对应的回滚操作就是把这条记录删除掉,更新了一条记录,对应的回滚操作就是把该记录更新回旧值;
因此,每当对一条记录进行改动的时候,都需要将回滚所需的东西记录下来,
- 插入一条记录时,要记录该记录的主键,便于回滚时根据主键删除记录
- 删除一条记录时,要把该记录的内容记下来,
- …
MySQL中规定,把用来记录事务回滚时所需信息的日志称为undo日志,由于select不会对记录进行改动,所以进行select操作时,不用记录undo日志.
1.2 事务id
如果某个事务在执行过程中对某个表执行了增删改操作,那么InnoDB存储引擎会为该事务分配一个独一无二的事务id,对于只读事务来说,只有在它对用户创建的临时表进行增删改查操作时,才会为该事务分配一个事务id,否则不会分配;
1.3 roll pointer 隐藏列
roll pointer作为用户记录的隐藏列,本质上是一个指向记录对应的undo日志的指针
1.4 trx_id 隐藏列
trx_id保存的是,对该条聚簇索引记录进行改动的语句所在的事务id
二、undo日志
2.1 undo日志的格式
在某些更新操作中,一条更新语句,可能对应多条undo日志,这些日志从0编号,被放在类型为FIL_PAGE_UNDO_LOG的页面中。
2.2 insert 对应的undo日志
- undo no:在一个事务中,undo no 从0开始递增,每生成一条undo日志,undo no增加1
- 主键个列信息:如果主键中包含多个列,会将这多个列占用的存储空间大小和对应的真实值记录下来
2.3 delete 操作对应的undo日志
向页面中插入的记录会根据记录头信息中的next_record属性组成一个单向链表,同样,被删除的记录也会根据记录的头信息中的next_record组成一个链表,只不过在该链表中的记录占用的空间可以被覆盖,所以也称为垃圾链表
通过delete语句删除一条记录分为以下两个阶段:
- 仅将记录的deleted_flag置为1,该阶段并不会将该记录加入到垃圾链表中,算是一条处于中间状态的记录
- 当该删除语句所在的事务提交之后,会将该记录头插到垃圾链表中,因此也会更改PAGR_FREE属性
因此delete语句对应的undo日志,只需考虑对阶段1所做的影响进行回滚就可以了。
注意:
在对一条记录进行delete mark操作前,需要把该记录的trx_id和roll_pointer隐藏列的旧值写到对应的undo日志中,这样有一个好处就是,可以通过undo日志的roll_pointer属性找到上一次对该记录进行改动时产生的undo日志
2.4 update操作对应的undo日志
在执行update语句时,InnoDB在对更新主键和不更新主键采取两种不同的方案
不更新主键的情况中也可分为被更新列占用的存储空间是否发生变化两种情况
(1)当被更新列占用的存储空间不发生变化时,采用就地更新的方式
(2)当被更新列占用的存储空间发生变化时,会先删除旧纪录,再插入新纪录,这里所说的删除不是delete mark,而是真正的删除
更新主键的情况
在聚簇索引中,记录之间根据主键进行排序,如果我们将索引从1更新成10000,并且在1-10000之间还有很多记录,此时主键值为1的记录和主键值为10000的记录之间会离的很远,对于这中情况,InnlDB分两步处理:
第一步:对旧纪录进行delete mark操作,记录一条类型为 TRX_UNDO_DEL_MARK_REC的undo 日志
第二步:插入一条新的记录到聚簇索引中,记录一条类型为TRX_UNDO_INSERT_REC的undo日志
也就是说,对更新一条记录的主键值时,会生成两条undo日志
2.5 Undo页面链表
在一个事务中,可能包含多个语句 ,所以在一个事务中可能产生很多的undo日志,这些日志可能一个页面中放不下,需要放到多个页面中,多个页面形成一个链表
InnoDB中规定,同一个undo页面中,要么只存储TRX_UNDO_INSERT大类的undo日志,要么只存储TRX_UNDO_UPDATE大类的undo日志,不能混着存,所以一个事务在执行过程中,需要2个undo页面的链表
2.6 undo日志写入过程
InnoDB中规定,每个Undo页面链表都对应着一个段,称为Undo Log Segment,链表中的页面都是从这个段中申请的,
Undo页面链表的第一个页面比普通页面多了一个Undo Log Segment Header
TRX_UNDO_STATE:当前页面链表处于什么状态
TRX_UNDO_LOG:Undo页面链表中最后一个Undo Log Header 的位置
TRX_UNDO_FSEG_HEADER:本Undo 页面链表对应的段的Segment Header信息
TRX_UNDO_PAGE_LIST:Undo页面链表的基节点
2.6.1 Undo Log Header
一个事务在向Undo页面中写入undo日志时,采用的方式很简单,直接往里"堆",写完一个Undo页面后,再从段中申请一个新页面
InnoDB中规定,同一事务向一个Undo页面链表中写入的undo日志算是一组
Undo Log Header 的结构:
TRX_UNDO_TRX_ID:生成本组undo日志的事务id
TRX_UNDO_TRX_NO:事务提交后生成的第一个序号
TRX_UNDO_DEL_MARKS:标记本组undo日志中是否包含由delete mark操作产生的undo 日志
TRX_UNDO_LOG_START:标记本组日志中第一条undo日志在页面中的偏移量
TRX_UNDO_NEXT_LOG:下一组undo日志在页面中开始的偏移量
TRX_UNDO_PREV_LOG:上一组undo日志在页面中开始的偏移量
2.7 回滚段
2.7.1 回滚段的概念
一个事务在执行的过程中最多可以分配4个Undo页面链表,因此同一时刻可以存在多个Undo页面链表,为了更好的管理这些Undo页面链表,InnoDB设计了一个名为Rollback Segment Header的页面,在这个页面中存放了各个Undo页面链表的first undo page的页号,这些页号称为undo slot
每一个Rollback Segment Header属于一个Rollback Segmeng段,同时,这个回滚段中其实只有一个页面
TRX_RSEG_MAX_SIZE:这个回滚段中所有undo链表中的所有undo页面之和的最大值
TRX_RSEG_HISTORY_SIZE:History链表占用的页面数量
TRX_RSEG_HISTORY:History链表的基节点
TRX_RSEG_FSEG_HEADER:这个回滚段对应的10字节大小的Segment Header结构
TRX_RSEG_UNDO_SLOTS:各个undo页面链表的first undo page的页号集合