20.2 事务id
对于只读事务来说,只有在它第一次对某个用户创建的临时表执行增、删、改操作时才会为这个事务分配一个事务id,否则的话是不分配事务id的
对于读写事务来说,只有在它第一次对某个表(包括用户创建的临时表)执行增、删、改操作时才会为这个事务分配一个事务id,否则也是不分配事务id的
聚簇索引的记录除了会保存完整的用户数据以外,而且还会自动添加名为trx_id
、roll_pointer
的隐藏列,如果用户没有在表中定义主键以及UNIQUE
键,还会自动添加一个名为row_id
的隐藏列
20.3 undo日志的格式
undo日志被记录到类型为FIL_PAGE_UNDO_LOG
的页面中
20.3.1 INSERT操作对应的undo日志
roll_pointer
本质上就是一个指向记录对应的 undo 日志的一个指针
聚簇索引记录被存储到了类型为FIL_PAGE_INDEX
的页面中,undo 日志被记录到类型为FIL_PAGE_UNDO_LOG
的页面中
20.3.2 DELETE操作对应的undo日志
插入到页面中的记录会根据记录头信息中的next_record
属性组成一个单向链表,称为正常记录链表
被删除的记录也会根据记录头信息中的next_record
属性组成一个链表,只不过这个链表中的记录占用的存储空间可以被重新利用,称这个链表 为垃圾链表
Page Header
部分有一个称之为PAGE_FREE
的属性,它指向由被删除记录组成的垃圾链表中的头节点
假设现在我们准备使用DELETE
语句把正常记录链表中的最后一条记录给删除掉,删除的过程需要经历两个阶段
仅仅将记录的
delete_mask
标识位设置为1,把这个阶段称之为delete mark
当该删除语句所在的事务提交之后,会有专门的线程后来真正的把记录删除掉。所谓真正的删除就是把该记录从正常记录链表中移除,并且加入到垃圾链表中,然后还要调整一些页面的其他信息,这个阶段称之为
purge
将被删除记录加入到垃圾链表时,实际上加入到链表的头节点处,会跟着修改PAGE_FREE
属性的值
20.3.3 UPDATE操作对应的undo日志
1、不更新主键
在不更新主键的情况下,又可以细分为被更新的列占用的存储空间不发生变化和发生变化的情况
- 就地更新
对于被更新的每个列来说,如果更新后的列和更新前的列占用的存储空间都一样大,那么就可以进行就地更新,也就是直接在原记录的基础上修改对应列的值。
要求每个列在更新前后占用的存储空间一样大,有任何一个被更新的列更新前比更新后占用的存储空间大,或者更新前比更新后占用的存储空间小都不能进行就地更新
- 先删除掉旧记录,再插入新记录
在不更新主键的情况下,如果有任何一个被更新的列更新前和更新后占用的存储空间大小不一致,那么就需要先把这条旧的记录从聚簇索引页面中删除掉,然后再根据更新后列的值创建一条新的记录插入到页面中
这里所说的删除并不是delete mark
操作,而是真正的删除掉,也就是把这条记录从正常记录链表中移除并加入到垃圾链表中,并且修改页面中相应的统计信息。真正删除之后紧接着就要根据各个列更新后的值创建的新记录插入
2、更新主键
在聚簇索引中,记录是按照主键值的大小连成了一个单向链表的,如果我们更新了某条记录的主键值,意味着这条记录在聚簇索引中的位置将会发生改变
针对UPDATE
语句中更新了记录主键值的这种情况,InnoDB 在聚簇索引中分了两步处理
将旧记录进行
delete mark
操作根据更新后各列的值创建一条新记录,并将其插入到聚簇索引中
20.3.4 增删改操作对二级索引的影响
- 对旧的二级索引记录执行
delete mark
操作 - 根据更新后的值创建一条新的二级索引记录,然后在二级索引对应的B+树中重新定位到它的位置并插入
20.5 FIL_PAGE_UNDO_LOG页面
FIL_PAGE_UNDO_LOG
类型的页面是专门用来存储 undo 日志的
undo 日志被分为两个大类:
TRX_UNDO_INSERT
:insert undo日志,提交之后可以直接删除TRX_UNDO_UPDATE
:update undo日志
20.6 undo页面链表
第一个 undo 页面称为 first undo page,其余页面称为 normal undo page
同一个 undo 页面要么只存储TRX_UNDO_INSERT
大类的 undo 日志,要么只存储TRX_UNDO_UPDATE
大类的 undo 日志,不能混着存储,所以一个称为 insert undo 链表,另一个称为 update undo 链表
不同事务执行过程中产生的 undo 日志需要被写入到不同的 undo 页面链表中
20.8 重用undo页面
事务提交后在某些情况下重用该事务的 undo 页面链表
- 该链表中只包含一个 undo 页面
- 该 undo 页面已经使用的空间小于整个页面空间的3/4
两种链表在被重用时的策略是不同的
- insert undo 链表中只存储类型为
TRX_UNDO_INSERT_REC
的 undo 日志,这种类型的 undo 日志在事务提交之后就没用了,就可以被清除掉。所以在某个事务提交后,重用这个事务的 insert undo 链表时,可以直接把之前事务写入的一组 undo 日志覆盖掉,从头开始写入新事务的一组 undo 日志 - update undo 链表:在一个事务提交后,它的 update undo 链表中的 undo 日志也不能立即删除掉(用于MVCC)。所以如果之后的事务想重用 update undo 链表,就不能覆盖之前事务写入的 undo 日志。这样就相当于在同一个 undo 页面中写入了多组的 undo 日志