书接上文,本篇介绍日志管理的最后一部分。
数据库UNDO日志记录格式
在存储已经搞定之后,那么还需要继续研究一个要写入的UNDO日志记录的格式是什么样子的。关于记录格式,之前也介绍过InnoDB表中行记录(Compact)的格式,也介绍了REDO日志的记录格式,其实都是本着省空间、高效率的宗旨来设计的,那么对于UNDO记录也是一样,但是因为UNDO日志有多个类型,针对不同的类型,其格式也不尽相同,UNDO日志的类型有下面几种。
- TRX_UNDO_INSERT_REC:记录插入的UNDO日志类型,插入记录用于回滚时,只需要通过其主键就可以实现回滚操作,所以在UNDO日志中,只记录了表ID及主键信息。回滚时,只需要通过记录中存储的主键,在原B+树中找到对应的记录,然后将其删除即可。
- TRX_UNDO_UPD_EXIST_REC:更新一条存在记录的UNDO日志类型。在日志内容中,需要记录的除了表ID信息之外,还需要记录每一个被更新的列的原始值和新值,同时还需要记录主键信息用于回滚时的检索。回滚时,还是根据主键信息,找到对应的记录,然后以旧换新,恢复原值即可。
- TRX_UNDO_UPD_DEL_REC:更新一条已经打了删除标志记录的UNDO日志类型。格式与上面是一样的,回滚方法也同上。
- TRX_UNDO_DEL_MARK_REC:删除记录时对记录打删除标志的UNDO日志类型,格式与上面插入操作的UNDO日志格式一样,只需要存储主键信息和表ID信息,用来在回滚或者PURGE时找到对应的记录即可。回滚时,根据主键信息,找到对应的记录,然后将删除标志去掉即完成回滚。
除了上面说到的Table ID信息、主键信息之外,还会包括一些公有的信息,比如回滚段指针、最近更新事务号,这样方便MVCC在回溯记录时可以找到以前的版本,关于MVCC的内容在这里就不详细展开了。
再回到记录格式。因为记录格式都不尽相同,所以这里只拿TRX_UNDO_INSERT_REC来举例说明,下图即为其格式。
每一个位置的解释如下。
- 可以看到在整个记录最前面的两个字节和最后面的两个字节是用来方便找到每一个记录的,并且通过这两条信息,就可以找到每一个UNDO页面中的所有记录,相当于是一个由UNDO记录组成的双向链表,因为对于UNDO记录,回滚过程是一个反向操作的过程,所以需要从后向前的搜索功能。
- 第二个位置存储的就是UNDO记录类型。
- 第三个位置存储的是一个事务的undo_no,用来区分一个事务中的多个UNDO日志的顺序。
- 接下来的位置用来存储当前回滚记录对应的表ID,接下来的trx_id存储的就是更新这条记录时的事务ID,即当前事务的事务ID。
- 再接着,roll_ptr用来存储当前被更新记录的上一个版本在回滚段中的位置,即这条记录中隐式列roll_ptr的值(用来在读取数据时可以找到老的版本),而当前记录的这个列的值,在写完这条UNDO日志之后,即将被修改为当前UNDO日志的位置,从而实现了一个隐式的单链表,可以使用roll_ptr的值一直回溯到第一次更新之前的版本。
- 再接下来的位置,存储的就是真实的主键信息了,存储格式是用前面若干个字节存储列数据的长度,而后面接着其数据,这样依次将所有的主键列存储完。
- 最后的位置,在第一点中已经介绍过了。
从图中可以看到,很多位置的存储都是压缩存储的,所以上面第六点说到,列数据长度用的字节个数有可能是若干个,这决定于InnoDB所使用的压缩编码方式。
这里需要注意的一点是,与REDO日志记录存储不同,UNDO日志的存储,是不会跨页面的,所以在页面头中关于日志存储的开始位置和结束位置就至关重要了。
其他类型的回滚记录,这里就不再介绍了,大致结构是一样的,只不过内容可能不尽相同。
需要注意的一点是,假如一个表中有多个索引,在修改一行数据时,回滚日志中也只会记录聚簇索引中的信息,而其他二级索引是不会被记录的。这是因为聚簇索引和二级索引中的每一行都是一一对应的,所以不同操作对聚簇索引操作时,也都会对二级索引有相应的操作,这样就没必要对二级索引写回滚日志了。
(注:配图为2017.5.6 ACMUG上海活动分析嘉宾,微博美女DBA)
回滚时刻
前面已经介绍过,UNDO日志的正确性是通过REDO的恢复来保证的,在REDO日志恢复完成之后,UNDO操作就可以安全地进行了。数据库启动过程中,执行了用于REDO恢复的函数recv_recovery_from_checkpoint_start之后,就可以处理UNDO的数据了,InnoDB通过函数trx_sys_init_at_db_start来将所有回滚段相关的128*1024个UNDO扫描出来(如果存在就找到,不存在就忽略),找到之后,每一个UNDO段的状态都已经清楚了,然后将它们都缓存起来。
然后再通过函数trx_lists_init_at_db_start依次处理每一个UNDO段,根据UNDO段的状态,决定后面将采取什么措施,如果状态为TRX_UNDO_PREPARED和TRX_UNDO_ACTIVE,则这个UNDO段是需要做回滚操作的,否则是不需要的。决定回滚需求之后,再将最多128*1024个UNDO段按照上面提到的TRX_UNDO_TRX_NO从大到小的顺序排序。
最后就在之前介绍关于InnoDB存储引擎启动时的函数recv_recovery_from_checkpoint_finish中,来做回滚的相关工作。在这个函数的最后可以看到以下内容。
它根据参数innodb_force_recovery来决定要不要做回滚操作,如果设置为3或3以上,就不回滚了,这样可能导致数据库逻辑上的不一致。
最终,InnoDB通过trx_rollback_or_clean_recovered来做回滚操作,通过扫描上面排序之后的链表,发现其还是以从大到小的顺序遍历,这个顺序很重要,因为UNDO是反向操作,所以应该是先处理新产生的事务,后处理老的事务,通过事务号来区分新老关系。
针对每一个UNDO段,InnoDB会将所有状态为ACTIVE的事务的UNDO日志扫描出来,然后一条一条地做回滚操作,UNDO日志记录格式已经明确,扫描所有的日志就变得非常简单,并且针对不同的操作,对应的回滚方式也已经清楚,等待所有的回滚段处理完成之后,整个数据库的回滚操作也就完成了。回滚过程如下图所示。
文章来自微信公众号:DBAce
本文链接:http://www.yunweipai.com/16455.html