【转】MySQL8.0 · 引擎分析 · InnoDB history list 无法降到0的原因

熟悉InnoDB的朋友都知道,innodb的history list长度代表了有多少undo日志还没有被清理掉,可以通过show engine innodb status 命令来获得。如果发现history list的长度越大,要么就是实例的复杂非常高,要么就是可能有大查询,或者事务没提交,导致Undo log无法分析。

但如果仔细观察,大家是否发现,history list居然无法降到0,即使做一次slow shutdown也不行。因为理论上来说,如果undo日志都已经purge干净了,理论上应该能下降为0。

为了更好的理解,我们先普及几个概念。首先innodb支持多个rollback segment,每个segment包含约1024个slot。

当事务开启时,会给它指定使用哪个rollback segment,然后在真正执行操作时,分配具体的slot,通常会有两种slot:

  • update_undo: 只用于事务内的update语句

  • insert_undo:只用于事务内的insert语句

通常如果事务内只包含一种操作类型,则只使用一个slot。但也有例外,例如insert操作,如果insert的记录在page上已经存在了,但是是无效的,那么久可以直接通过更新这条无效记录的方式来实现插入,这时候使用的是update_undo.

为什么要分成两种undo slot,而不是只用一个slot处理所有呢?这是因为在提交阶段的undo处理不同:

对于Insert undo, 有两种处理方式

  • Free: 直接清理掉,因为我们知道新插入的记录产生的Undo不会被任何查询语句所引用,因此可以直接释放undo,这里的undo log不会累加到history list上

  • reuse: 当undo 只占用一个page,且page使用低于一定比例时(事实上,第二个条件对于insert undo可以移除掉),放到cachd list上,以备重用。 在重用时,会将该page reset掉

    对于update_undo: 也有两种处理方式:

  • Purge: 这里会加入到其对应rollback segment的history list数据页列表上,history list长度加1

  • Reuse: 同样会将undo加到history list上,history list长度加1。by the way, update undo和insert的重用方式不同,它会在undo page上新建一个undo log header, 而不是重置page。这意味着一个undo页上可能有多个undo log分属不同的事务,但只有一个可能是活跃的。

那么回到最初的问题,既然undo log都加到history list了,为啥在undo purge完成后,未重置为0呢?

我们来看看如下函数

    trx_purge_truncate
      trx_purge_truncate_history
                trx_purge_truncate_rseg_history

在函数trx_purge_truncate_rseg_history中,有如下代码段:

        if ((mach_read_from_2(seg_hdr + TRX_UNDO_STATE) == TRX_UNDO_TO_PURGE)
            && (mach_read_from_2(log_hdr + TRX_UNDO_NEXT_LOG) == 0)) {

                /* We can free the whole log segment */

                mutex_exit(&(rseg->mutex));
                mtr_commit(&mtr);

                trx_purge_free_segment(rseg, hdr_addr, n_removed_logs);

                n_removed_logs = 0;
        } else {
                mutex_exit(&(rseg->mutex));
                mtr_commit(&mtr);
        }

这里做了特殊判断,只有状态为PURGE的undo log才做了free segment清理。对于cached状态的undo留在原地。个人猜测是因为这些undo log可以留作重用, 在重用之后,再做一次性清理。

为了验证猜测,修改函数trx_undo_set_state_at_finish,使undo log状态,要么为TRX_UNDO_TO_FREE, 要么为TRX_UNDO_TO_PURGE。

在给实例加了一定的负载,再做一次slow shutdown重启后,history list length的长度果然变成了0。验证了其无法重置为0是由于cached undo导致。











本文转自 lirulei90 51CTO博客,原文链接:http://blog.51cto.com/lee90/2070637,如需转载请自行联系原作者
上一篇:生成指定大小文件


下一篇:MySQL · 源码分析 · Innodb 引擎Redo日志存储格式简介