【轉】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導致。