31 KiB
事务
事务分类
事务通常可分为如下类型:
- 扁平事务(flat transaction)
- 带保存点的扁平事务(flat transaction with savepoints)
- 链事务(chained transaction)
- 嵌套事务(nested transaction)
- 分布式事务(distributed transaction)
扁平事务
扁平事务为最常用的事务,在扁平事务中,所有操作都处于统一层次,其由begin work开始,并由commit work或rollback work结束。
扁平事务的操作是原子的,要么都提交,要么都回滚。
带有保存点的扁平事务
对于带有保存点的扁平事务,其支持在事务执行过程中回滚到同一事务中较早的一个状态。
在事务执行过程中,可能并不希望所有的操作都回滚,放弃所有操作的代价可能太大。通过保存点,可以记住事务当前的状态,在后续发生错误后,事务能够回到保存点当时的状态。
相较于扁平事务只能够全部回滚,带保存点的扁平事务能够回滚到保存点时的状态。
保存点可以通过save work来创建,使用示例如下所示
链事务
可视为保存点事务的一个变种。在使用带保存点的事务时,如果系统发生崩溃,那么所有的保存点都会消失。在后续重启进行恢复时,事务需要从开始处重新执行,而不是从最近的一个保存点开始执行。
若事务在数据库未提交时发生崩溃,那么在数据库再次重启执行recovery操作时,会对未提交的事务进行回滚,即使之前事务存在保存点,也会全部回滚
链事务的思想是,当提交事务时,释放不必要的数据对象,将必要的上下文隐式传递给下一个要开始的事务。提交事务和开始下一个事务操作必须为原子操作,下一个事务必须徐要能看到上一个事务的结果,示例如下:
和带保存点的扁平事务不同的是,带保存点的扁平事务能够回滚到任意正确的保存点,而链事务只能回滚当前事务。
且链事务和带保存点的扁平事务,对于锁的处理也不同:
链事务:对于每个事务,commit后释放持有的锁带保存点的扁平事务:在整个事务提交前,不会释放持有的锁
嵌套事务
嵌套事务为一个层次结构框架,由顶层事务控制各个层次的事务。嵌套在顶层事务中的事务被称为子事务。
如下为Moss理论嵌套事务的定义:
- 嵌套事务是由若干事务组成的一颗树,子树既可以是嵌套事务,又可以是扁平事务
- 处在叶子节点的事务是扁平事务
- 位于根节点的事务被称为顶层事务,其他事务被称为子事务,事务的
predecessor被称为父事务,事务的下一层事务被称为子事务 - 子事务既可以提交又可以回滚,但是子事务的提交并不会立马生效,除非其父事务已经被提交。
任何子事务都在顶层事务提交后才真正提交 - 树中任何一个事务的回滚会引其所有子事务都一起回滚
在Moss理论中,实际工作被交由叶子节点来完成,只有叶子节点的事务才能够访问数据库,发送消息,获取其他类型的资源。高层事务仅仅负责逻辑控制,即负责何时调用相关子事务。
即使一个系统不支持嵌套事务,也可以通过保存点技术来模拟嵌套事务。
savepoint和嵌套事务区别
在使用保存点来模拟嵌套事务时,在锁持有方面和嵌套事务有差别。
- 嵌套事务:在使用嵌套事务时,不同子事务在数据库持有的锁不同
- 保存点:在通过保存点来模拟嵌套事务时,用户无法选择哪些锁被哪些子事务继承,无论有多少个保存点,所有的锁都可以得到访问
分布式事务
通常是在分布式环境运行的扁平事务,需要访问网络中的不同节点。
分布式事务同样需要满足ACID的特性,要么都发生,要么都不发生。
对于innodb存储引擎,其支持扁平事务、带有保存点的扁平事务、链事务、分布式事务。innodb并不原生支持嵌套事务,单可以通过带保存点的事务来模拟串行的嵌套事务。
事务实现
对于事务的ACID特性,其实现如下:
I(隔离性):事务隔离性通过锁来实现A(原子性), D(持久性):事务的原子性和持久性可以通过redo log来实现C(一致性):事务一致性通过undo log来实现
redo
redo log(重做日志)用于实现事务的持久性,其由两部分组成:
- 内存中的重做日志缓冲(redo log buffer)
- 磁盘中的重做日志文件(redo log file)
redo log persists before transaction committed
innodb存储引擎支持事务,其通过force log at commit机制实现事务的持久性,即当事务提交时,必须先将该事务的所有日志写入到磁盘的日志文件中进行持久化,直到该过程完成后事务才提交完成。
上述描述中
提交时将事务所有日志写入到磁盘的日志文件中,这句话中日志代表redo log 和 undo log。
- redo log用于保证事务持久性
- undo log用于帮助事务回滚,也用于mvcc功能
redo log和undo log比较
- redo log在mysql server运行时,只会被顺序的写入,server运行时并不会被读取
- undo log则不同,在server运行时可能需要对事务进行回滚或执行mvcc操作,此时可能需要对undo log文件进行
随机读写
redo log和binlog的比较
binglog通常用于对数据库进行point in time形式的恢复(从某个时间点起恢复数据)以及主从复制;但是,binlog和redo log的差别如下:
- binlog针对的是mysql数据库级别,不止用于innodb,还用于其他存储引擎
- redo log属于innodb存储引擎级别
- binlog实际记录的是对应sql语句,属于逻辑日志
- redo log实际记录的格式则是物理格式,具体为针对某个页面的物理修改
redo log和 bin log日志写入磁盘的时机也有所不同:
- bin log
仅在事务提交后才进行一次写入 - redo log
在事务执行过程中也可能发生写入(redo log buffer满后会写入到磁盘中),故而,redo log file中同一事务写入的redo log内容可能并非是连续的,多个事务在写入redo log时可能会交叉写入
innodb_flush_log_at_trx_commit
参数innodb_flush_log_at_trx_commit用于控制redo log/ undo log刷新到磁盘的策略,该参数默认值为1,即事务提交时刷新日志到磁盘。除了默认值之外,还可以为该参数设置如下值:
- 0: 事务提交时不刷新日志到磁盘,仅在master thread中刷新日志到磁盘,master thread中刷新操作每秒触发一次
- 2:事务提交时刷新日志,但是仅将日志写入到文件系统的缓存中,
并不进行fsync操作
将innodb_flush_log_at_trx_commit参数设置为0或2虽然可以在一定幅度上提高性能,但是会丧失数据库的ACID特性。
log block
在innodb中,redo都是以512字节的大小为单位进行存储的,即redo log buffer、redo log file都是以block的形式进行保存,block的大小为512字节。
block & atomic
若针对相同的页,redo log的大小大于512字节,那么其会被分割为多个block进行存储。且由于redo log block的大小和磁盘扇区相同,故而在将block时,无需使用double write机制,针对特定的block,起写入为原子的,要么写入成功要么写入失败,不会像页(page)一样存在dirty的情况。
redo log block中包含的内容除了日志本身外,还包含log block header和log block tailer内容。log block header + log block content + log block tailer合计占用512字节,其中,各部分大小如下:
log block header: 12字节大小log block content: 492字节大小log block tailer: 8字节大小
如上所示,每个redo log block可实际存储的内容大小为492字节。
log block header
log block header大小为12字节,由如下部分组成:
LOG_BLOCK_HDR_NO: 占用4字节LOG_BLOCK_HDR_DATA_LEN: 占用2字节LOG_BLOCK_FIRST_REC_GROUP: 占用2字节LOG_BLOCK_CHECKPOINT_NO: 占用4字节
LOG_BLOCK_HDR_NO
log buffer由log block所组成,可以将log buffer看作是log block的数组,故而,log block header中LOG_BLOCK_HDR_NO起代表当前block在buffer中的位置。
LOG_BLOCK_HDR_NO由于表示的是log buffer中的数组小标,故而可知LOG_BLOCK_HDR_NO其是递增的,并且可以循环使用。LOG_BLOCK_HDR_NO的大小为4字节,但是其首位用作flush bit,故而,其可表示的最大长度为2^31 bytes = 2GiB。
LOG_BLOCK_HDR_DATA_LEN
LOG_BLOCK_HDR_DATA_LEN大小为2字节,代表log block所占用的大小,当log block被写满时,该值为0x200,表示当前log block使用完block中所有的可用空间,即log block的大小为512字节。
LOG_BLOCK_FIRST_REC_GROUP
LOG_BLOCK_FIRST_REC_GROUP占用2个字节,表示log block中第一个日志所处的偏移量。
LOG_BLOCK-FIRST_REC_GROUP的取值可能存在如下场景:
- 该值大小和
LOG_BLOCK_HDR_DATA_LEN相同,则代表当前block中不包含新的日志(即当前block中存储的存储的全是上一block中record的后续部分)
下图表示事务T1的重做日志占用762字节,事务T2的重做日志占用100字节的场景。
由于每个block中最多只能保存492字节的数据,故而T1事务的762字节需要分布在两个block中,第一个block保存492字节的数据,第二个block中保存剩余270字节的数据。
- 其中,左侧的block,其
LOG_BLOCK_FIRST_REC_GROUP值为12,代表第一个record开始的位置紧接在log block header之后 - 而右侧的block,其
LOG_BLOCK_FIRST_REC_GROUP的值为12 + 270 = 282 bytes。在存放第一条record之前,不仅有log block header对应的12字节,还有之前T1剩余日志的270字节
LOG_BLOCK_CHECKPOINT_NO
LOG_BLOCK_CHECKPOINT_NO占用4字节大小,代表log block最后被写入时的检查点第四字节的值。
LSN(log sequence number)为一个全局唯一且单调递增的64位数字,当发生数据修改时,redo log内容会增加,此时LSN也会增加。
CHECKPOINT则是一个LSN值,同样为64位整数,代表位于CHECKPOINT之前所有的修改已经被持久化到数据库中,位于CHECKPOINT之前的redo log内容可以被安全的覆盖。
log block tailer
log block tailer中仅由一个部分组成,即LOG_BLOCK_TRL_NO,其值和LOG_BLOCK_HDR_NO相同。
log group
log group被称为重做日志组,其中包含多个redo log文件,innodb中只有一个log group。
log group只是一个逻辑上的概念,由多个redo log file组成。log group中每个redo log file大小相同。redo log file中存储的是redo log block,在innodb引擎运行过程中,会将redo log buffer中的log block刷新到磁盘文件中。
redo log buffer刷新到磁盘中的时机
redo log buffer会在如下时机将log block刷新到磁盘中:
- 事务提交时
- log buffer中有一半的内存空间已经被使用时
- log checkpoint时(checkpoint时会导致脏页被刷新到磁盘上,而WAL要求脏页刷新前刷新redo log buffer)
WAL
write-ahead logging是一种为database系统提供原子性与持久性的技术。
write ahead log是append-only的辅助磁盘存储结构,用于crash recovery和transaction recovery。
在使用WAL的系统中,在所有的changes被应用到数据库之前,要求changes都被写入到log中。
所以,在innodb中,脏页被刷新到磁盘之前,脏页对应的newest_lsn之前的redo log都必须被刷新到磁盘中。redo log file中最新的lsn必须大于磁盘页文件中最大的lsn。
在redo log buffer中的log block刷新到redo log file中时,其会追加(append)到redo log file的末尾。当redo log group中的一个文件被写满时,其会接着写入下一个redo log file,其行为称为round-robin。
在redo log group中的每个redo log file中,其前2KB(4个log block大小)均不用于存储log block,前2KB内容如下:
- 对于log group中的第一个redo log file,前2KB用于存储如下内容,下列每个部分大小均为一个block:
- log file header
- checkpoint 1
- 空
- checkpoint2
- 对于log group中
非第一个redo log file,其仅保留开头2KB的空间,但并不保存信息
redo log format
innodb中存储管理是基于页的,故而redo log format格式也基于页。
redo log头部格式
redo log头部格式通常包含3部分:
redo_log_type: 重做日志类型- space: 表空间id
- page_no: 页的偏移量
之后redo log body部分,根据重做日志类型的不同会存储不同内容。
LSN
LSN代表的是日志序列号,其大小为8字节,且单调递增。LSN代表事务写入redo log的总字节数。
页LSN
在每个页的头部,都存在FIL_PAGE_LSN,其记录了该页的lsn。在页中,LSN代表该页最后刷新时的lsn大小。
FIL_PAGE_LSN在buffer pool page和disk page中均存在,二者记录的值不同:
buffer pool pageheader中FIL_PAGE_LSN记录的是内存页最后被修改的LSNdisk pageheader中FIL_PAGE_LSN记录的是最后被刷新到磁盘的页对应的最大修改LSN
在执行crash recovery过程中,会从CHECKPOINT开始,一直到redo log file末尾,逐条处理redo log record,对于每条redo log record关联的页,会比较record_lsn和FIL_PAGE_LSN的大小:
record_lsn <= FIL_PAGE_LSN:代表当前redo record对应的修改已经包含在页中,当前redo log record直接跳过即可record_lsn > FIL_PAGE_LSN:代表当前redo record中的修改不存在于页中,需要对页应用record修改,并在修改完后更新页的FIL_PAGE_LSN
查看LSN
可以通过show engine innodb status来查看LSN情况,核心数值如下:
log sequence number:代表当前LSNlog flushed up to: 代表已经刷新到磁盘文件中的LSNlast checkpoint at: 代表页已经刷新到磁盘的LSN
recovery
innodb在启动时,不管上次数据库运行是否正常关闭,都会尝试执行恢复。
redo log是物理日志,故而恢复速度相较逻辑日志要快得多,恢复操作仅需从checkpoint开始。
例如,对于如下数据表
create table t (
a int,
b int,
primary key(a),
key(b)
);
若执行sql语句
insert into t select 1,2;
在执行时,需要修改如下内容:
- 聚簇索引页(包含数据)
- 辅助索引页
故而,其记录重做日志内容大致为
page(2,3), offset 32, value 1,2; # 聚簇索引页
page(2,4), offset 64, value 2; # 辅助索引页
由上述示例可知,redo log为物理日志,记录的是对页的物理修改,故而redo log是幂等的。
undo
redo log记录了对页的物理操作,可以用于进行redo。而undo和redo不同,undo主要用于对事务的回滚。
undo的存放位置和redo不同:
- redo log存放在redo log file中
- undo log存放在数据库内部的segment中,该segment被称为
undo segment。undo段位于共享的表空间内
undo log和redo log差异
- redo log为物理日志,记录的是对页的修改;而undo log则是逻辑日志,对每个insert操作,undo log会生成一个相反的delete,对update也会生成另一个逆向的update
- redo log是全局的,innodb中所有事务都会
向同一个redo log交叉写入;而undo log则是针对事务的,每个事务都有其自己的undo log chain
非锁定读
除了用于事务回滚外,undo log还可以用于MVCC。当事务A尝试读取一条记录R时,如果记录R已经被另一个事务B占用,那么事务A可以通过undo log读取行数据之前的版本。
上述实现被称为非锁定读。
undo log的产生会伴随redo log的产生
undo log其本质仍然是数据。undo log其存放在表空间的undo segment中,仍然可被可做是数据,而WAL(write-ahead logging)要求变更被应用到数据库之前,需要先写入日志。
故而,在生成undo log时,对于undo页的修改也会被记录到redo log中。
存储管理
innodb通过segment来管理undo log,其管理方式如下:
- innodb包含rollback segment
- 每个rollback segment会记录1024个undo log segment
- undo log segment中会进行undo页的申请
- 共享表空间偏移量为5的页会记录所有rollback segment header所在的页
- 偏移量为5的页类型为FIL_PAGE_TYPE_SYS
innodb_undo_directory
该参数用于设置rollback segment文件所在的路径,默认为./,代表datadir。
如果innodb_undo_directory变量没有被定义,那么undo tablespace将会被创建再datadir下。默认情况下,undo tablespaces文件的名称为undo_001和undo_002。
innodb_rollback_segments
每个undo tablespace支持最大128个rollback segments,innodb_rollback_segments变量定义了rolback segments的数量。
每个rollback segments支持的事务数量由rollback segment中undo slot的数量和每个事务需要的undo log数量来决定。
当innodb页大小为16KB时,rollback segment中undo slot的数量为
innodb page size/ 16,即1024个。易知,每个slot占用的大小为
16bit,即长度为2字节。故而,
slot中存放的是指向undo segment的句柄,而不是存放undo segment本身。
innodb_undo_tablespaces
该变量设置了undo tablespaces的数量。
purge
在事务提交之后,并不能立刻删除undo log以及undo log所在的页,其他事务仍有可能通过undo log来还原数据行的之前版本。故而,在事务提交时,会将undo log放入到一个链表中,交由purge线程来决定是否最终删除undo log以及undo log所在的页。
purge代表`清空不再被需要的旧版本数据行及其对应的undo log记录。
如果为每一个事务分配一个单独的undo页,那么会非常浪费存储空间。由于事务提交时,所分配的undo页并不能立刻释放,故而,当数据库负载较大时,可能同时存在大量的undo页,会占用相当多的存储空间。
undo页的重用设计
在innodb对undo页的设计中,考虑了对undo页的重用。当事务提交时,首先会将undo log放在链表中,然后判断undo页的使用空间是否小于3/4。如果是,代表该undo页可以被重用,之后新的undo log会记录在当前undo log的后面。
即undo page是可被重用的,当事务提交时,如果undo log的使用空间小于3/4,那么该undo页是可以被重用的,一个undo页中可能包含多个undo log
核心概念
rollback segment
undo tablespace由rollback segment构成,每个undo tablespace最多支持128个rollback segment,innodb_rollback_segments定义了rollback segments的数量。
undo slots
undo slot是rollback segment内的slot,由rollback segment进行管理。
undo slot主要用于关联undo segment,当事务启动时,系统会从rollback segment中获取一个空闲的undo slot,成功获取undo slot后即代表关联了一个undo segment。
每个undo log slot中会存储一个page_no,其指向undo log segment的起始页位置。
undo segment
undo segment为undo slot指向的空间,undo segment中包含多个undo pages,而undo segment中则包含了undo log。
undo log格式
在innodb存储引擎中,undo log分为如下两种类型:
- insert undo log:
- insert undo log是事务在insert操作中产生的undo log
- 在事务提交之前,事务插入的数据对其他事务不可见;而事务提交之后,事务插入的数据对其他读已提交的事务才可见;故而,insert undo log在事务提交之后不再被需要,因为在读已提交隔离级别下,insert undo log是可见的;在可重复读的隔离级别下,insert undo log则是不可见的,没有中间版本,只能可见/不可见
- 故而,当事务提交之后,即可删除该事务关联的insert undo log
- update undo log
insert undo log
在事务提交之后,insert undo log即可被删除,故而无需purge操作。
insert undo log结构如下:
- next:下一个undo log的位置,长度为2字节
- type_cmpl: 记录undo的类型,对于insert undo来说,该值为11。该字段长度为1字节
- undo_no:记录事务的id(压缩后保存)
- table_id: 记录undo log对应的表对象(压缩后保存)
- 记录所有主键的列和值(本次插入的数据,可能多条)
- start:位于undo log尾部,记录undo log的开始位置,长度为2字节
rollback
在执行rollback操作时,可以根据insert undo log中存储的table id,主键列、主键值来定位需要回滚的行数据,直接删除回滚数据即可。
update undo log
update undo log针对的是delete和update操作。在mvcc机制的实现中,需要用到该undo log,故而update undo log在事务提交后不能立刻删除。
update undo log的格式如下图所示。
对于update undo log,其哥字段含义如下:
- type_cmpl: 对update undo log,其
type_cmpl可能的取值如下:- 12:TRX_UNDO_UPD_EXIST_REC,更新未被标记为delete的记录
- 13:TRX_UNDO_UPD_DEL_REC,更新
已经被标记为delete的记录 - 14:TRX_UNDO_DEL_MARK_REC,将记录标记为delete
- update vector: update vector中记录了update操作导致发生改变的列,每个被修改的列信息都记录在undo log中。
在执行delete操作时,并不会直接对行数据进行物理删除操作,而是
将行数据标记为delete,待后续purge操作中才会实际对数据进行物理删除。
undo log中主要存储旧的col值,用于在回滚或mvcc时为undo操作提供信息,还原数据先前的版本。
在实际生成insert/update undo log时,对于插入、更新、删除等操作,其实际生成undo log的方式如下:
- insert操作:
- 对于insert操作,其实际生成一条insert undo log,
type_cmpl的值为TRX_UNDO_INSERT_REC
- 对于insert操作,其实际生成一条insert undo log,
- delete操作:
- 对于delete操作,其实际生成一条
type_cmpl值为TRX_UNDO_DEL_MARK_REC的undo log - delete操作并不会直接对记录进行物理删除,而是将记录标记为delete,后续进行purge操作时才会实际删除
- 对于delete操作,其实际生成一条
- update记录的非主键值:
- 在对记录的非主键值进行update时,会生成一条
type_cmpl值为TRX_UNDO_UPD_EXIST_REC的记录,用于对更新操作进行回滚
- 在对记录的非主键值进行update时,会生成一条
- update记录的主键值
- 当对记录主键列进行修改时,会生成两条undo log:
TRX_UNDO_DEL_MARK_REC类型的记录,对应将原记录标记为删除的回滚TRX_UNDO_INSERT_REC类型的记录,标记对新纪录的插入回滚
- 当对记录主键列进行修改时,会生成两条undo log:
对于insert/update undo log record,每条undo record均只针对一条记录。故而,当一条sql语句对多行记录进行删除/更改时,会生成多条insert/update undo record。
undo log组织形式
上述内容介绍了undo log record的格式,为了对undo log records进行高效率的访问和管理,undo log record应当以适当的形式被组织。
undo log的逻辑组织方式
每个事务都会修改一系列的records,并产生相应的undo log records,事务对应的undo log由这些undo log records组成。
undo log除了由undo log record组成外,其还包含了一个undo log header,结构如下:
如上述结构所示,undo log header中包含如下部分:
- trx id: 事务id,用于标识产生该undo log的事务
- trx no: trx no用于表示事务的提交顺序(header中trx no并非一定有值,在事务提交前,该field未定义,当事务提交后,该field会被填充)
trx no用于判断是否purge应当被执行
- delete mark: 判断是否undo log中存在
TRX_UNDO_DEL_MARK_REC类型的undo record,从而在purge时避免扫描 - log start offset: 该field记录了undo log header结尾的位置,便于后续对undo log header进行拓展
- History List Node:undo log通过该结构挂载到History List中
故而,undo log就粒度存在两种结构
- undo record: 记录每行数据的历史版本,行数据通过维护undo record chain来还原历史版本
- undo log: 针对事务级别,每个undo log由undo log header和多个undo records组成,并且undo log会根据事务的提交顺序被挂载到history list中
record versions
如果多个事务针对同一条record进行了修改(串行),那么,每个事务对record造成的修改都会生成不同的版本(version),不同的版本之间通过链表进行链接,用于后续的mvcc,示例如下:
如上所示,事务I, J, K都对id=1的记录进行了操作,其操作顺序如下:
- 事务I插入了id为1的记录,并且将
a的值设置为A - 事务J将id为1记录的
a修改为了B - 事务K将id为1记录的
a修改为了C
事务I, J, K都有其事务对应的undo log,并且每个undo log都拥有对应的undo log header和undo log records。
通过index中的记录及其rollptr引用的链表,可以对记录关联的I, J, K三个版本进行还原。
物理组织方式
undo log的结构如上述所示,我们无法控制事务生成undo log的大小(当事务操作大量的数据时,其undo log对应的undo records数量也会变多,undo log大小更大)。但是,最终生成undo log写入磁盘时,会基于固定的大小进行写入(16KB)。
由于事务关联undo log的大小是无法控制的,其大小可能需要多个页来进行存储。故而,对占用空间较大的undo log,undo page会按照需要进行分配;而对于大小较小的undo log,会将多个undo log放置在同一个undo page中。
undo log和undo page的对应关系是灵活的,既可能一个undo log占用多个undo pages,也可能多个undo log共用相同的undo page
undo segment
每当一个事务开启时,都需要持有一个undo segment,对于undo segment中磁盘空间的释放和占用(对16KB页的释放和占用)都由FSP segment进行管理。
undo segment中会至少持有一个undo page,并且,每个undo page都会记录undo page header。
undo page header
undo page header中包含如下内容:
- undo page type:undo page的类型
- last log record offset:最后一条记录的offset
- free space offset:page中空闲空间的offset
- undo page list node:指向List中的下一个undo page
undo segment中的第一个undo page除了undo page header外,还会记录undo segment header。
undo segment header
undo segment header中包含如下内容:
- state:该field记录了udno segment的状态(TRX_UNDO_CACHED/TRX_UNDO_PURGE)
- undo segment中最后一条undo record的位置
- 当前segment被分配的undo page组成的链表
undo log storing
undo page中的空间用于存储undo log,对于大小较小的undo loginnodb会对undo page进行reuse,在undo page中存储多个undo logs避免浪费空间。而对于大小较大的undo log,会使用多个undo page来对该undo log进行存储。(undo page reuse只会发生在segment的第一个page)。
文件组织方式
在同一时刻,一个undo segment只属于一个事务,一个undo segment无法同时被多个事务共享。每当事务开启时,都会获取一个undo segment,故而,在多个事务并行的运行时,需要多个undo segment。
rollback segment
在innodb中,每个rollback segment中包含了1024个undo slot,而每个undo tablespace最多包含128个rollback segment。
rollback segment header中包含了128个slot,每个slot包含4字节,并且都指向某个undo segment的第一页。
undo slot的数量会影响innodb数据库中的事务并行程度,
purge
表t中,a为聚簇索引,b为辅助索引,若执行如下sql
delete from t where a=1;
上述语句造成的修改如下:
- 将主键
a为1的记录标记为delete,将记录delete flag设置为1(聚簇索引中的数据并没有被实际物理删除),且会生成针对聚簇索引的undo log。当事务发生回滚时,仅需将delete flag重新设置为0即可完成对删除的回滚。 - 对于辅助索引中满足
a=1的记录,同样不会做任何处理,也不会产生针对辅助索引的undo log
undo log只针对聚簇索引生成,对于辅助索引的变化并不会生成对应的undo log。
从上文的undo log内容来看,update undo log和insert undo log都记录了唯一索引(聚簇索引)的值,并且update undo log记录了各修改列的oldValue,二者均不涉及辅助索引内容。
可知在delete语句执行时,并不会马上就对记录进行物理删除,而是将记录标记为delete,记录实际的删除在purge操作时才被执行。
对于记录的delete操作和update操作(update主键列时,会先将原记录标记为删除,后插入一条新的记录),会在purge操作中完成。purge确保了innodb存储引擎对于MVCC机制的支持,记录不能再事务提交时立刻进行处理,仍会有其他事务会访问该记录的旧版本。
是否可以对记录进行物理删除由purge来决定,若某行记录不被其他任何事务引用,可以执行物理delete操作。
history
innodb存储引擎中维护了一个history列表,其根据事务的提交顺序对undo log进行链接,在history list中,先提交的事务其undo log位于history list的尾端。
