【MySQL】 日志 - undo
【MySQL】 日志 - undo
Metadata
title: 【MySQL】 日志 - undo
date: 2023-06-25 16:14
tags:
- 行动阶段/完成
- 主题场景/数据存储
- 笔记空间/KnowladgeSpace/ProgramSpace/BasicsSpace
- 细化主题/数据存储
categories:
- 数据存储
keywords:
- 数据存储
description: 【MySQL】 日志 - undo
概述
回滚是指将已经提交的事务撤销,恢复到事务开始之前的状态。保证数据的一致性和完整性。
把回滚时所需的东西都给记下来
把这些为了回滚而记录的这些东东称之为撤销日志,英文名为undo log
事务id
如果某个事务执行过程中对某个表执行了增、删、改操作,那么InnoDB 存储引擎就会给它分配一个独一无二的事务id
在内存中维护一个全局变量,每当需要为某个事务分配一个事务id 时,就会把该变量的值当作事务id 分配给该事务,并且把该变量自增1。
trx_id隐藏列
roll pointer隐藏列的含义
本质上就是一个指向记录对应的undo日志的一个指针。
undo日志的格式
- INSERT操作对应的undo日志
- 写对应的undo 日志时,主要是把这条记录的主键信息记上。
- 写对应的undo 日志时,主要是把这条记录的主键信息记上。
- DELETE操作对应的undo日志
- 被删除的记录其实也会根据记录头信息中的next_record 属性组成一个链表,只不过这个链表中的记录占用的存储空间可以被重新利用,所以也称这个链表为垃圾链表。
- 被删除的记录其实也会根据记录头信息中的next_record 属性组成一个链表,只不过这个链表中的记录占用的存储空间可以被重新利用,所以也称这个链表为垃圾链表。
- UPDATE操作对应的undo日志
- 不更新主键的情况 — 更新后的列和更新前的列占用的存储空间
- 就地更新(in-place update)
- 先删除掉旧记录,再插入新记录
- 更新主键的情况
- 将旧记录进行delete mark 操作
- 根据更新后各列的值创建一条新记录,并将其插入到聚簇索引中(需重新定位插入的位置)。
- 不更新主键的情况 — 更新后的列和更新前的列占用的存储空间
FIL_PAGE_UNDO_LOG页面
Undo页面链表
undo日志具体写入过程
每一个Undo页面链表都对应着一个段,称之为Undo Log Segment 。
为事务分配Undo页面链表详细过程
- 回滚段分配:
- 在事务对普通表的记录进行改动之前,会先从系统表空间的第5号页面中分配一个回滚段。
- 一旦某个回滚段被分配给了事务,后续对普通表的记录改动不会重复分配。
- 回滚段分配方式:
- 使用循环方式(round-robin)分配回滚段,依次分配给不同的事务。
- 缓存的undo slot:
- 分配到回滚段后,会检查回滚段的缓存链表中是否有已经缓存的undo slot。
- 根据操作类型(如INSERT或DELETE)选择相应的缓存链表进行查找。
- 如果存在缓存的undo slot,则将其分配给当前事务。
- 如果没有可用的缓存undo slot,则在Rollback Segment Header页面中查找可用的undo slot。
- 分配可用的undo slot:
- 从Rollback Segment Header页面中开始查找可用的undo slot。
- 从第0个undo slot开始,如果其值为FIL_NULL,则表示该undo slot为空闲,将其分配给当前事务。
- 如果第0个undo slot不满足条件,则继续查找下一个undo slot,直到找到可用的undo slot。
- 如果所有的1024个undo slot都不为空闲,则报错。
- Undo Log Segment和Undo页面链表:
- 如果分配到的undo slot是从缓存链表中获取的,那么对应的Undo Log Segment已经分配。
- 否则,需要重新分配一个Undo Log Segment,并从该Undo Log Segment中申请一个页面作为Undo页面链表的第一个页面。
- 写入Undo日志:
- 事务可以将undo日志写入先前申请的Undo页面链表中。
事务回滚的需求
出现问题
- 情况一:事务执行过程中可能遇到各种错误,比如服务器本身的错误,操作系统错误,甚至是突然断电导致的错误。
- 情况二:程序员可以在事务执行过程中手动输入ROLLBACK 语句结束当前的事务的执行。
回滚是指将已经提交的事务撤销,恢复到事务开始之前的状态。保证数据的一致性和完整性。
把回滚时所需的东西都给记下来
把这些为了回滚而记录的这些东东称之为撤销日志,英文名为undo log
由于查询操作( SELECT )并不会修改任何用户记录,所以在查询操作执行时,并不需要记录相应的undo日志。
事务id
给事务分配id的时机
如果某个事务执行过程中对某个表执行了增、删、改操作,那么InnoDB 存储引擎就会给它分配一个独一无二的事务id
分配方式如下
- 对于只读事务来说,只有在它第一次对某个用户创建的临时表执行增、删、改操作时才会为这个事务分配一个事务id ,否则的话是不分配事务id 的。
- 对于读写事务来说,只有在它第一次对某个表(包括用户创建的临时表)执行增、删、改操作时才会为这个事务分配一个事务id ,否则的话也是不分配事务id 的。
事务id是怎么生成的
这个事务id 本质上就是一个数字,它的分配策略和我们前边提到的对隐藏列row_id (当用户没有为表创建主键 和UNIQUE 键时InnoDB 自动创建的列)的分配策略大抵相同,具体策略如下:
- 服务器会在内存中维护一个全局变量,每当需要为某个事务分配一个事务id 时,就会把该变量的值当作事务id 分配给该事务,并且把该变量自增1。
- 每当这个变量的值为256 的倍数时,就会将该变量的值刷新到系统表空间的页号为5 的页面中一个称之为Max Trx ID 的属性处,这个属性占用8 个字节的存储空间。
- 当系统下一次重新启动时,会将上边提到的Max Trx ID 属性加载到内存中,将该值加上256之后赋值给我们前边提到的全局变量(因为在上次关机时该全局变量的值可能大于Max Trx ID 属性值)。
这样就可以保证整个系统中分配的事务id 值是一个递增的数字。先被分配id 的事务得到的是较小的事务id ,后被分配id 的事务得到的是较大的事务id 。
trx_id隐藏列
聚簇索引的记录除了会保存完整的用户数据以外,而且还会自动添加名为trx_id、roll_pointer的隐藏列,如果用户没有在表中定义主键以及UNIQUE键,还会自动添加一个名为row_id的隐藏列。
undo日志的格式
为了实现事务的原子性,
InnoDB 存储引擎在实际进行增、删、改一条记录时,都需要先把对应的undo日志记下来。
一般每对一条记录做一次改动,就对应着一条undo日志,但在某些更新记录的操作中,也可能会对应着2 条undo日志
INSERT操作对应的undo日志
插入的结果就是这条记录被放到了一个数据页中。
如果希望回滚这个插入操作,那么把这条记录删除就好了,也就是说在写对应的undo 日志时,主要是把这条记录的主键信息记上。
设计了一个类型为 TRX_UNDO_INSERT_REC 的undo日志
- undo no 在一个事务中是从0 开始递增的,也就是说只要事务没提交,每生成一条undo日志,那么该条日志的undo no 就增1。
- 如果记录中的主键只包含一个列,那么在类型为TRX_UNDO_INSERT_REC 的undo日志中只需要把该列占用的存储空间大小和真实值记录下来,如果记录中的主键包含多个列,那么每个列占用的存储空间大小和对应的真实值都需要记录下来(图中的len 就代表列占用的存储空间大小, value 就代表列的真实值)。
当我们向某个表中插入一条记录时,实际上需要向聚簇索引和所有的二级索引都插入一条记录。不过记录undo日志时,我们只需要考虑向**聚簇索引**插入记录时的情况就好了,因为其实聚簇索引记录和二级索引记录是一一对应的,我们在回滚插入操作时,只需要知道这条记录的主键信息,然后**根据主键信息做对应的删除操作**,做删除操作时就会顺带着把所有二级索引中相应的记录也删除掉。后边说到的DELETE操作和UPDATE操作对应的undo日志也都是针对聚簇索引记录而言的,我们之后就不强调了。
为了最大限度的节省undo日志占用的存储空间,和我们前边说过的redo日志类似,设计InnoDB的大叔会给undo日志中的某些属性进行压缩处理,具体的压缩细节我们就不唠叨了。
title: 示例
```sql
# 插入两条记录
INSERT INTO undo_demo(id, key1, col)
VALUES (1, 'AWM', '狙击枪'), (2, 'M416', '步枪');
```
第一条undo日志的undo no 为0 ,记录主键占用的存储空间长度为4 ,真实值为1 。画一个示意图就是这样:

第二条undo日志的undo no 为1 ,记录主键占用的存储空间长度为4 ,真实值为2 。画一个示意图就是这样(与第一条undo日志对比, undo no 和主键各列信息有不同):

roll pointer隐藏列的含义
本质上就是一个指向记录对应的undo日志的一个指针。
roll_pointer 本质就是一个指针,指向记录对应的undo日志。
DELETE操作对应的undo日志
插入到页面中的记录会根据记录头信息中的next_record 属性组成一个单向链表,我们把这个链表称之为正常记录链表
被删除的记录其实也会根据记录头信息中的next_record 属性组成一个链表,只不过这个链表中的记录占用的存储空间可以被重新利用,所以也称这个链表为垃圾链表。
假设此刻某个页面中的记录分布情况是这样的
页面的Page Header 部分的PAGE_FREE 属性的值代表指向垃圾链表头节点的指针。
假设现在我们准备使用DELETE 语句把正常记录链表中的最后一条记录给删除掉,其实这个删除的过程需要经历两个阶段:
阶段一:仅仅将记录的delete_mask 标识位设置为1 ,其他的不做修改(其实会修改记录的trx_id 、roll_pointer 这些隐藏列的值)。设计InnoDB 的大叔把这个阶段称之为delete mark 。
正常记录链表中的最后一条记录的delete_mask值被设置为1,但是并没有被加入到垃圾链表。也就是此时记录处于一个中间状态
阶段二:当该删除语句所在的事务提交之后,会有专门的线程后来真正的把记录删除掉。
所谓真正的删除就是把该记录从正常记录链表中移除,并且加入到垃圾链表中,然后还要调整一些页面的其他信息,比如页面中的用户记录数量PAGE_N_RECS 、上次插入记录的位置PAGE_LAST_INSERT 、垃圾链表头节点的指针PAGE_FREE 、页面中可重用的字节数量PAGE_GARBAGE 、还有页目录的一些信息等等。设计InnoDB 的大叔把这个阶段称之为purge 。
在删除语句所在的事务提交之前,只会经历阶段一,也就是delete mark 阶段(提交之后我们就不用回滚了,所以只需考虑对删除操作的阶段一做的影响进行回滚)。
注意一下这几点:
- 在对一条记录进行delete mark 操作前,需要把该记录的旧的trx_id 和roll_pointer 隐藏列的值都给记到对应的undo日志中来,就是我们图中显示的old trx_id 和old roll_pointer 属性。
这样有一个好处,那就是可以通过undo日志的old roll_pointer 找到记录在修改之前对应的undo 日志。比方说在一个事务中,我们先插入了一条记录,然后又执行对该记录的删除操作,这个过程的示意图就是这样:
undo 日志串成了一个链表。这个链表就称之为版本链
- 与类型为TRX_UNDO_INSERT_REC 的undo日志不同,类型为TRX_UNDO_DEL_MARK_REC 的undo 日志还多了一个索引列各列信息的内容
如果某个列被包含在某个索引中,那么它的相关信息就应该被记录到这个索引列各列信息部分
所谓的相关信息包括该列在记录中的位置(用pos 表示),该列占用的存储空间大小(用len 表示),该列实际值(用value 表示)。
title: 示例
```sql
BEGIN; # 显式开启一个事务,假设该事务的id为100
# 插入两条记录
INSERT INTO undo_demo(id, key1, col)
VALUES (1, 'AWM', '狙击枪'), (2, 'M416', '步枪');
# 删除一条记录
DELETE FROM undo_demo WHERE id = 1;
```

UPDATE操作对应的undo日志
在执行UPDATE 语句时, InnoDB 对更新主键和不更新主键这两种情况有截然不同的处理方案。
不更新主键的情况
- 就地更新(in-place update)
更新记录时,对于被更新的每个列来说,如果更新后的列和更新前的列占用的存储空间都一样大,那么就可以进行就地更新: 直接在原记录的基础上修改对应列的值
- 先删除掉旧记录,再插入新记录
如果有任何一个被更新的列更新前和更新后占用的存储空间大小不一致,那么就需要先把这条旧的记录从聚簇索引页面中删除掉,然后再根据更新后列的值创建一条新的记录插入到页面中。
删除并不是delete mark 操作,而是真正的删除掉,也就是把这条记录从正常记录链表中移除并加入到垃圾链表中,并且修改页面中相应的统计信息(比如PAGE_FREE 、PAGE_GARBAGE 等这些信息)
设计了一种类型为TRX_UNDO_UPD_EXIST_REC 的undo日志
它的完整结构如下:
注意这么几点:
- n_updated 属性表示本条UPDATE 语句执行后将有几个列被更新,后边跟着的<pos, old_len, old_value> 分别表示被更新列在记录中的位置、更新前该列占用的存储空间大小、更新前该列的真实值。
- 如果在UPDATE 语句中更新的列包含索引列,那么也会添加索引列各列信息这个部分,否则的话是不会添加这个部分的。
title: 示例
```sql
BEGIN; # 显式开启一个事务,假设该事务的id为100
# 插入两条记录
INSERT INTO undo_demo(id, key1, col)
VALUES (1, 'AWM', '狙击枪'), (2, 'M416', '步枪');
# 删除一条记录
DELETE FROM undo_demo WHERE id = 1;
# 更新一条记录
UPDATE undo_demo
SET key1 = 'M249', col = '机枪'
WHERE id = 2;
```

更新主键的情况
- 将旧记录进行delete mark 操作
在UPDATE 语句所在的事务提交前,对旧记录只做一个delete mark 操作
在事务提交后才由专门的线程做purge操作,把它加入到垃圾链表中。
- 根据更新后各列的值创建一条新记录,并将其插入到聚簇索引中(需重新定位插入的位置)。
由于更新后的记录主键值发生了改变,所以需要重新从聚簇索引中定位这条记录所在的位置,然后把它插进去。
通用链表结构
在写入undo日志的过程中会使用到多个链表,很多链表都有同样的节点结构
- Pre Node Page Number 和Pre Node Offset 的组合就是指向前一个节点的指针
- Next Node Page Number 和Next Node Offset 的组合就是指向后一个节点的指针。
整个List Node 占用12 个字节的存储空间。
为了更好的管理链表,设计InnoDB 的大叔还提出了一个基节点的结构,里边存储了这个链表的头节点、尾节点以及链表长度信息,基节点的结构示意图如下:
其中:
- List Length 表明该链表一共有多少节点。
- First Node Page Number 和First Node Offset 的组合就是指向链表头节点的指针。
- Last Node Page Number 和Last Node Offset 的组合就是指向链表尾节点的指针。
整个List Base Node 占用16 个字节的存储空间。
undefined
FIL_PAGE_UNDO_LOG页面
表空间其实是由许许多多的页面构成的. 专门用来存储undo日志的, 称之为FIL_PAGE_UNDO_LOG 类型的
Undo Page Header 是Undo页面所特有的
- TRX_UNDO_PAGE_TYPE :本页面准备存储什么种类的undo日志。
- TRX_UNDO_INSERT (使用十进制1 表示):类型为TRX_UNDO_INSERT_REC 的undo日志属于此大类,一般由INSERT 语句产生,或者在UPDATE 语句中有更新主键的情况也会产生此类型的undo日志。
- TRX_UNDO_UPDATE (使用十进制2 表示):除了类型为TRX_UNDO_INSERT_REC 的undo日志,其他类型的undo日志都属于这个大类,比如我们前边说的TRX_UNDO_DEL_MARK_REC 、TRX_UNDO_UPD_EXIST_REC 啥的,一般由DELETE 、UPDATE 语句产生的undo日志属于这个大类。
- TRX_UNDO_PAGE_START :表示在当前页面中是从什么位置开始存储undo日志的,或者说表示第一条undo日志在本页面中的起始偏移量。
- TRX_UNDO_PAGE_FREE :与上边的TRX_UNDO_PAGE_START 对应,表示当前页面中存储的最后一条undo 日志结束时的偏移量,或者说从这个位置开始,可以继续写入新的undo日志。
- TRX_UNDO_PAGE_NODE: 代表一个List Node 结构
Undo页面链表
单个事务中的Undo页面链表
每条记录进行改动前,都需要记录1条或2条的undo日志,所以在一个事务执行过程中可能产生很多undo日志,这些日志可能一个页面放不下,需要放到多个页面中,这些页面就通过我们上边介绍的TRX_UNDO_PAGE_NODE 属性连成了链表:
大家往上再瞅一瞅上边的图,我们特意把链表中的第一个Undo页面给标了出来,称它为first undo page ,其余的Undo页面称之为normal undo page ,这是因为在first undo page 中除了记录Undo Page Header 之外,还会记录其他的一些管理信息,这个我们稍后再说哈。
在一个事务执行过程中,可能混着执行INSERT 、DELETE 、UPDATE 语句,也就意味着会产生不同类型的undo日志。
同一个Undo页面要么只存储TRX_UNDO_INSERT 大类的undo日志,要么只存储 TRX_UNDO_UPDATE 大类的undo日志
在一个事务执行过程中就可能需要2个Undo页面的链表,一个称之为insert undo链表,另一个称之为update undo链表,
另外,设计InnoDB 的大叔规定对普通表和临时表的记录改动时产生的undo日志要分别记录,所以在一个事务中最多有4个以Undo页面为节点组成的链表:
并不是在事务一开始就会为这个事务分配这4个链表,具体分配策略如下:
- 刚刚开启事务时,一个Undo页面链表也不分配。
- 当事务执行过程中向普通表中插入记录或者执行更新记录主键的操作之后,就会为其分配一个普通表的insert undo链表。
- 当事务执行过程中删除或者更新了普通表中的记录之后,就会为其分配一个普通表的update undo链表。
- 当事务执行过程中向临时表中插入记录或者执行更新记录主键的操作之后,就会为其分配一个临时表的insert undo链表。
- 当事务执行过程中删除或者更新了临时表中的记录之后,就会为其分配一个临时表的update undo链表。
总结: 按需分配,啥时候需要啥时候再分配,不需要就不分配。
多个事务中的Undo页面链表
不同事务执行过程中产生的undo日志需要被写入到不同的Undo页面链表中
undo日志具体写入过程
段(Segment)的概念
整个Segment Header 占用10个字节大小,各个属性的意思如下:
- Space ID of the INODE Entry : INODE Entry 结构所在的表空间ID。
- Page Number of the INODE Entry : INODE Entry 结构所在的页面页号。
- Byte Offset of the INODE Ent : INODE Entry 结构在该页面中的偏移量
Undo Log Segment Header
每一个Undo页面链表都对应着一个段,称之为Undo Log Segment 。
可以看到这个Undo 链表的第一个页面比普通页面多了个Undo Log Segment Header
- TRX_UNDO_STATE :本Undo页面链表处在什么状态。
- 一个Undo Log Segment 可能处在的状态包括:
- TRX_UNDO_ACTIVE :活跃状态,也就是一个活跃的事务正在往这个段里边写入undo日志。
- TRX_UNDO_CACHED :被缓存的状态。处在该状态的Undo页面链表等待着之后被其他事务重用。
- TRX_UNDO_TO_FREE :对于insert undo 链表来说,如果在它对应的事务提交之后,该链表不能被重用,那么就会处于这种状态。
- TRX_UNDO_TO_PURGE :对于update undo 链表来说,如果在它对应的事务提交之后,该链表不能被重用,那么就会处于这种状态。
- TRX_UNDO_PREPARED :包含处于PREPARE 阶段的事务产生的undo日志。
- TRX_UNDO_LAST_LOG :本Undo页面链表中最后一个Undo Log Header 的位置。
- TRX_UNDO_FSEG_HEADER :本Undo页面链表对应的段的Segment Header 信息(就是我们上一节介绍的那个10字节结构,通过这个信息可以找到该段对应的INODE Entry )。
- TRX_UNDO_PAGE_LIST : Undo页面链表的基节点。
Undo Log Header
同一个事务向一个Undo页面链表中写入的undo日志算是一个组
设计InnoDB 的大叔把存储这些属性的地方称之为Undo Log Header
所以Undo页面链表的第一个页面在真正写入undo日志前,其实都会被填充Undo Page Header 、Undo Log Segment Header 、Undo Log Header 这3个部分
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 日志中第一条undo日志的在页面中的偏移量。
- TRX_UNDO_XID_EXISTS :本组undo日志是否包含XID信息。
- TRX_UNDO_DICT_TRANS :标记本组undo日志是不是由DDL语句产生的。
- TRX_UNDO_TABLE_ID :如果TRX_UNDO_DICT_TRANS 为真,那么本属性表示DDL语句操作的表的table id 。
- TRX_UNDO_NEXT_LOG :下一组的undo日志在页面中开始的偏移量。
- TRX_UNDO_PREV_LOG :上一组的undo日志在页面中开始的偏移量。
- TRX_UNDO_HISTORY_NODE :一个12字节的List Node 结构,代表一个称之为History 链表的节点。
小结
对于没有被重用的Undo页面链表来说,链表的第一个页面,也就是first undo page 在真正写入undo日志前,会填充Undo Page Header 、Undo Log Segment Header 、Undo Log Header 这3个部分,之后才开始正式写入undo日志。
normal undo page 在真正写入undo日志前,只会填充Undo Page Header 。
链表的List Base Node 存放到first undo page 的Undo Log Segment Header 部分, List Node 信息存放到每一个Undo页面的undo Page Header 部分
重用Undo页面
为每个事务单独分配相应的Undo页面链表(最多可能单独分配4个链表)
在事务提交后在某些情况下重用该事务的Undo页面链表。一个Undo页面链表是否可以被重用的条件很简单:
- 该链表中只包含一个Undo页面。
只有在Undo页面链表中只包含一个Undo 页面时,该链表才可以被下一个事务所重用。
- 该Undo页面已经使用的空间小于整个页面空间的3/4。
回滚段
回滚段的概念
在这个页面中存放了各个Undo页面链表的frist undo page 的页号,他们把这些页号称之为undo slot 。
title: 理解
每个Undo页面链表都相当于是一个班,这个链表的first undo page 就相当于这个班的班长,找到了这个班的班长,就可以找到班里的其他同学(其他同学相当于normal undo page )。有时候学校需要向这些班级传达一下精神,就需要把班长都召集在会议室,这个Rollback Segment Header 就相当于是一个会议室。
每一个Rollback Segment Header 页面都对应着一个段,这个段就称为Rollback Segment ,翻译过来就是回滚段。
Rollback Segment Header 的页面的各个部分的含义都是啥意思:
- TRX_RSEG_MAX_SIZE :本Rollback Segment 中管理的所有Undo页面链表中的Undo页面数量之和的最大值。
- TRX_RSEG_HISTORY_SIZE : History 链表占用的页面数量。
- TRX_RSEG_HISTORY : History 链表的基节点。
- TRX_RSEG_FSEG_HEADER :本Rollback Segment 对应的10字节大小的Segment Header 结构,通过它可以找到本段对应的INODE Entry 。
- TRX_RSEG_UNDO_SLOTS :各个Undo页面链表的first undo page 的页号集合,也就是undo slot 集合。
从回滚段中申请Undo页面链表
多个回滚段
不同的回滚段可能分布在不同的表空间中
回滚段的分类
针对普通表和临时表划分不同种类的回滚段的原因:在修改针对普通表的回滚段中的Undo页面时,需要记录对应的redo日志,而修改针对临时表的回滚段中的Undo页面时,不需要记录对应的redo日志。
为事务分配Undo页面链表详细过程
- 事务在执行过程中对普通表的记录首次做改动之前,首先会到系统表空间的第5 号页面中分配一个回滚段(其实就是获取一个Rollback Segment Header 页面的地址)。一旦某个回滚段被分配给了这个事务,那么之后该事务中再对普通表的记录做改动时,就不会重复分配了。
- 使用传说中的round-robin (循环使用)方式来分配回滚段。比如当前事务分配了第0 号回滚段,那么下一个事务就要分配第33 号回滚段,下下个事务就要分配第34 号回滚段,简单一点的说就是这些回滚段被轮着分配给不同的事务(就是这么简单粗暴,没啥好说的)。
- 在分配到回滚段后,首先看一下这个回滚段的两个cached链表有没有已经缓存了的undo slot ,比如如果事务做的是INSERT 操作,就去回滚段对应的insert undo cached链表中看看有没有缓存的undo slot ;如果事务做的是DELETE 操作,就去回滚段对应的update undo cached链表中看看有没有缓存的undo slot 。如果有缓存的undo slot ,那么就把这个缓存的undo slot 分配给该事务。如果没有缓存的undo slot 可供分配,那么就要到Rollback Segment Header 页面中找一个可用的undo slot 分配给当前事务。
- 从Rollback Segment Header 页面中分配可用的undo slot 的方式我们上边也说过了,就是从第0 个undoslot 开始,如果该undo slot 的值为FIL_NULL ,意味着这个undo slot 是空闲的,就把这个undo slot分配给当前事务,否则查看第1 个undo slot 是否满足条件,依次类推,直到最后一个undo slot 。如果这1024 个undo slot 都没有值为FIL_NULL 的情况,就直接报错喽(一般不会出现这种情况)~
- 找到可用的undo slot 后,如果该undo slot 是从cached链表中获取的,那么它对应的Undo LogSegment 已经分配了,否则的话需要重新分配一个Undo Log Segment ,然后从该Undo Log Segment 中申请一个页面作为Undo页面链表的first undo page 。
- 然后事务就可以把undo日志写入到上边申请的Undo页面链表了!
对临时表的记录做改动的步骤和上述的一样,就不赘述了。
不错需要再次强调一次,如果一个事务在执行过程中既对普通表的记录做了改动,又对临时表的记录做了改动,那么需要为这个记录分配2个回滚段。并发执行的不同事务其实也可以被分配相同的回滚段,只要分配不同的undoslot就可以了。