引用《InnoDB存储引擎》中有一句话,特别重要:
用户通常对undo有这样的误解:undo用于将数据库物理地恢复到执行语句或事务之前的样子---但事实并非如此。
undo是逻辑日志,因此只是将数据库逻辑地恢复到原来的样子。所有的修改都被逻辑地取消了,但是数据库和页本身在回滚之后可能大不相同。
1、初始化回滚段
回滚段的初始化在InnoDB引擎启动时就已初始化好,它主要通过三个函数调用,最终在表空间初始化函数srv_undo_ tablespaces_init中,初始化了128个回滚段:
2、分配回滚段
当开启一个事务后,需要给当前的事务分配一个回滚段,调用它的函数流程关系为:
trx_assign_rseg-> trx_assign_rseg_low-> get_next_noredo_rseg:对于只读事务,如果产生对临时表的写入,则需要为其分配回滚段,使用临时表回滚段(第1~32号回滚段)。
trx_set_rw_mode-> trx_assign_rseg_low-> get_next_redo_rseg:在MySQL5.7中事务默认以只读事务开启,当随后判定为读写事务时,则转换成读写模式,并为其分配事务ID和回滚段。
在源码中的具体代码为:
普通回滚段的分配方式如下:
- 采用round-robin的轮询方式来赋予回滚段给事务,如果回滚段被标记为skip_allocation,则跳到下一个;
- 选择一个回滚段给事务后,该回滚段的rseg->trx_ref_count会递增,这样该回滚段所在的undo tablespace文件就不可以被truncate掉;
- 临时表回滚段被赋予trx->rsegs->m_noredo,普通读写操作的回滚段被赋予trx->rsegs->m_redo;如果事务在只读阶段使用到临时表,随后转换成读写事务,那么会为该事务分配两个回滚段。
3、使用回滚段
使用undo log时也需要判断undo log的类型,记录下变更前的数据以维护多版本信息。insert 和 delete/update 分开记录undo log,因此需要从回滚段单独分配undo slot。
从下面源代码中可以看出,无论是TRX_UNDO_INSERT_OP还是TRX_UNDO_MODIFY_OP类型,都调用trx_undo_assign_undo函数。
函数trx_undo_assign_undo的流程如下:
4、写入undo log
将undo log写入到回滚段的总流程如下:
省略不重要的步骤,单看最重要的函数调用:
分析:
通过trx_undo_report_row_operation函数,首先会分配了一个undo slot,调用的函数为trx_undo_assign_undo。分配分配好slot同时初始化完可用的空闲区域后,就可以向其中写入undo记录了。写入的page no取自undo->last_page_no,初始情况下和hdr_page_no相同。然后根据undo log的类型来判断是分配insert undo log还是update undo log。
对于INSERT_UNDO,调用函数trx_undo_page_report_insert进行插入,对于UPDATE_UNDO,调用函数trx_undo_page_report_modify进行插入。
比如,对于较为复杂的update undo log来说,写入undo log的过程是通过一个指针ptr将各种信息写入到指针指向的区域,从源代码中trx_undo_page_report_modify函数可以看到具体的实现:
后面会有循环在不断往ptr所指向的地址写各个字段的数据,
…….
然后,会将指针写入上一个和下一个回滚日志记录:
最后将UNDO日志中更改的信息写入REDO日志,这也证明了写undo log时也会写redo log。undo log也需要持久性的保护。
完成undo log写入后,构建新的回滚段指针并返回(trx_undo_build_roll_ptr),回滚段指针包括undo log所在的回滚段id、日志所在的page no、以及page内的偏移量,需要记录到聚集索引记录中。
下面是两次实测结果:
已知开启的事务已经执行完3次update操作,第一张图中,当前事务在第4次update时,top_page_no不变,仍在331号页,但是,top_offset偏移量从2192变为2228,并且top_undo_no由2变3;第二张图中,此事务在第5次update操作时,top_page_no不变,仍在331号页,但是,top_offset偏移量从2228变为2264,并且top_undo_no由3变4。
分析可知:写undo log记录时,在当前的undo页偏移位置开始写undo log记录,通过偏移量来记录undo log,并且top_undo_no每次记录顺序加1,这也证明了undo log顺序写。
下面两张图实测了update操作十万多次的结果:
可以看到:随着undo log写入的越多,top_page_no也在不断变大。