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

  • 2019 年 10 月 4 日
  • 筆記

摘自阿里雲部落格,原文地址:https://yq.aliyun.com/articles/400891?spm=a2c4e.11153959.blogcont341036.25.7d001098OH5qbi

熟悉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導致。

Tags: