Percona bug#1058100提到trx_purge_add_update_undo_to_history函数,不甚了解,用gdb跟踪了下
trx_commit_off_kernel->trx_write_serialisation_history->trx_undo_update_cleanup->trx_purge_add_update_undo_to_history
lsn = trx_write_serialisation_history(trx);
a.首先对当前事务的undo段进行标记,以表明事务提交。
对于update undo日志,为事务分配id(trx_serialisation_number_get->trx_sys_get_new_trx_id),标记undo状态为TRX_UNDO_TO_PURGE(trx_undo_set_state_at_finish),
对于insert undo标记undo回滚段状态为TRX_UNDO_TO_FREE
如果update undo段只占用一个Page,并且使用的字节数小于TRX_UNDO_PAGE_REUSE_LIMIT(3/4的page size)时,则将undo段标记为TRX_UNDO_CACHED,表示下次可以重用该回滚段。
b.对于update undo,还需要将undo加到history list上,并做一些清理工作
trx_undo_update_cleanup(trx, undo_hdr_page, &mtr);
(1)trx_purge_add_update_undo_to_history(trx, undo_page, mgr);
当undo 段的state不为TRX_UNDO_CACHED时,需要更新undo段的history list length,然后将undo加入到改回滚段的TRX_RSEG_HISTORY的链表上(不太了解undo这部分相关的逻辑),将事务号写入undo_header + TRX_UNDO_TRX_NO中,并在TRX_UNDO_DEL_MARKS标注是否存在标记删除记录(需要purge)
在kernel_mutex的保护下trx_sys->rseg_history_len++;
我们在show engine innodb status里看到的History list length实际上就是trx_sys->rseg_history_len值。
由于该函数总是在事务提交时才被调用到,因此我们也可以把history list lengh理解为尚未被清理update undo的事务数.在update/delete为主的工作负载中,可能会看到length明显的增大。
这里有一段被注释的代码,也是Percona bug#1058100提出的质疑
// if (!(trx_sys->rseg_history_len % srv_purge_batch_size)) { /*should wake up always*/
/* Inform the purge thread that there is work to do. */
srv_wake_purge_thread_if_not_active();
// }
也就是说,在Percona版本中,purge线程总是会被唤醒。在原版MySQL5.5中这个判断逻辑还在。
官方5.6则和Percona版本类似,去除了判断条件。但5.6会在递增rseg_history_len之后,在写入undo_header事务Id之前唤醒purge线程。不知是否有潜在风险
如果每次都无条件的唤醒,这可能破坏了innodb_purge_batch_size的定义。
(2)从事务回滚段上把update undo移除
UT_LIST_REMOVE(undo_list, rseg->update_undo_list, undo);
(3)如果undo的state为TRX_UNDO_CACHED,则将其加入到事务回滚段的rseg->update_undo_cached中,留作下次重用。
UT_LIST_ADD_FIRST(undo_list, rseg->update_undo_cached, undo);
否则直接释放undo内存结构trx_undo_mem_free(undo);
//////////////////////////////////////////////////////////////////////////////////////////
既然我们知道了history list length来自trx_sys->rseg_history_len,那么在何时,这个值会被递减呢?
有两个函数被涉及到trx_purge_truncate_rseg_history以及trx_purge_free_segment
调用栈如下:
trx_purge->que_run_threads->que_run_threads_low->row_purge_step->row_purge->trx_purge_fetch_next_rec->trx_purge_truncate_if_arr_empty->trx_purge_truncate_history->trx_purge_truncate_rseg_history->trx_purge_free_segment
当purge线程清理undo时,会遍历所有的回滚段列表(trx_purge_truncate_history, 最多1024个回滚段,在内存中用try_sys->rseg_list表示),进行undo清理。会对清理的trx_no 和undo_no做限制。
函数trx_purge_truncate_rseg_history有两个参数:
limit_trx_no,移除所有事务id小于这个值的undo log
limit_undo_no,如果事务id等于limit_trx_no,则只清理undo_no小于该值的undo 日志。
需要purge的Undo Log被记录在每个回滚段头的TRX_RSEG_HISTORY中,通过遍历列表,来回收Undo 记录。