MySQL/InnoDB trx_rseg_history_len !=0的探究

1. 故事的起源

我們在學(xué)習(xí)MySQL/InnoDB purge的過程中,使用select name, subsystem, count from information_schema.innodb_metrics where name="trx_rseg_history_len";查看系統(tǒng)當(dāng)前回滾段歷史鏈表長度(可以把這個歷史鏈表近似理解為:尚未被清理的Undo物理頁面),效果如下:

然而,我們發(fā)現(xiàn)即使在無負載時,trx_rseg_history_len也不會降低為0,這一點,網(wǎng)上早有反饋https://bugs.mysql.com/bug.php?id=76750,阿里印風(fēng)給出了解答https://yq.aliyun.com/articles/400891?spm=a2c4e.11155435.0.0.23f84f18pf0kqh。我們在MySQL8.0.3-rc版本上發(fā)現(xiàn)了此問題,所以基于MySQL8.0.3-rc,我們展開討論。

2.trx_rseg_history_len是什么?

我們可以將trx_rseg_history_len近似地理解為系統(tǒng)中尚未被清理的Undo物理頁面數(shù)。
查詢trx_rseg_history_len,實際查詢的是trx_sys.rseg_history_len,那么trx_sys.rseg_history_len在什么條件下增長、什么條件下縮減呢?

2.1 trx_sys.rseg_history_len的增長

事務(wù)提交時,update類型的undo頁面將被添加到歷史鏈表,此時trx_sys->rseg_history_len隨之+1。

trx_commit-->trx_commit_low-->trx_write_serialisation_history-->trx_undo_update_cleanup-->trx_purge_add_update_undo_to_history-->os_atomic_increment_ulint(&trx_sys->rseg_history_len, 1)

2.2 trx_sys.rseg_history_len的縮減

Purge線程清理Undo頁面時,將Undo頁從歷史鏈表移除,此時trx_sys->rseg_history_len隨之-1。

 srv_do_purge-->trx_purge-->trx_purge_truncate-->trx_purge_truncate_history-->trx_purge_truncate_rseg_history-->trx_purge_free_segment-->trx_purge_remove_log_hdr-->os_atomic_decrement_ulint(&trx_sys->rseg_history_len, 1)

3. Purge的工作機制?

既然trx_sys->rseg_history_len不能降回0,那么我們就關(guān)注Purge為何不能將其降0,從Purge線程說起。

3.1 Purge協(xié)調(diào)線程

Purge coordinator線程,其大致邏輯如下:

srv_purge_coordinator_thread //Purge coordinator線程函數(shù)主體
  srv_do_purge
    trx_purge
      srv_que_task_enqueue_low //如果需要的話,喚醒一些Purge工作線程
      que_run_threads //協(xié)調(diào)線程本身也purge數(shù)據(jù)行
      trx_purge_truncate //清理Undo表空間,和2.2對應(yīng)上了!

3.2 Purge工作線程

聚焦trx_purge_truncate上下文,不介紹Purge工作線程

3.3 為什么不能降0?

說回到2.2的函數(shù)調(diào)用過程,單說下面這部分,只要執(zhí)行trx_purge_truncate,一定會調(diào)用后續(xù)函數(shù)(期間無分支跳出此調(diào)用過程),最終trx_sys->rseg_history_len - 1。

trx_purge_truncate-->trx_purge_truncate_history-->trx_purge_truncate_rseg_history-->trx_purge_free_segment-->trx_purge_remove_log_hdr-->os_atomic_decrement_ulint(&trx_sys->rseg_history_len, 1)

那么,進入trx_purge_truncate的條件是什么?
trx_purge調(diào)用trx_purge_truncate:

trx_purge(ulint n_purge_threads, ulint batch_size, bool truncate)
{
  ...
  if (truncate || srv_upgrade_old_undo_found) {
    trx_purge_truncate();
  }
  ...
}

srv_do_purge調(diào)用trx_purge:

srv_do_purge(ulint n_threads, ulint* n_total_purged)
{
  ...
  n_pages_purged = trx_purge(n_use_threads, 
                    srv_purge_batch_size, 
                    (++count % rseg_truncate_frequency) == 0);
  ...
}

可以看到,滿足(++count % rseg_truncate_frequency) == 0則進入trx_purge_truncate。
那么count是什么?rseg_truncate_frequency又是什么?

srv_do_purge(ulint n_threads, ulint* n_total_purged)
{
  ...
  static ulint count = 0; //count記錄了執(zhí)行trx_purge的次數(shù)
  ...
  ulint rseg_truncate_frequency = ut_min(
            static_cast<ulint>(srv_purge_rseg_truncate_frequency),
            undo_trunc_freq); //rseg_truncate_frequency是truncate undo表空間的頻率,缺省值128
}

count和rseg_truncate_frequency共同實現(xiàn)了:每執(zhí)行rseg_truncate_frequency次trx_purge,truncate一次undo表空間,清理undo物理頁面。

3.4 小結(jié)

如果Purge線程執(zhí)行了n次,n%rseg_truncate_frequency != 0,則n%rseg_truncate_frequency個Undo頁面得不到清理,導(dǎo)致:

trx_sys->rseg_history_len = n % rseg_truncate_frequency

比如,trx_purge調(diào)用127次即清理完全部Undo信息,則這127個Undo頁面,就不被清理。

4. 實驗

為驗證上述想法,我們修改了srv_do_purge,使得每次執(zhí)行trx_purge都truncate表空間(這樣會帶來大量小I/O,影響性能)。

srv_do_purge(ulint n_threads, ulint* n_total_purged)
{
  ...
  n_pages_purged = trx_purge(n_use_threads, 
                    srv_purge_batch_size, 1);
  ...
}

我們使用Sysbench對此實例壓測,產(chǎn)生足量Undo信息后等待Purge線程清理完成,最終可觀察到,trx_sys->rseg_history_len = 0,如下示:


5. 最后

5.1 結(jié)論

為了減少I/O,Purge線程每執(zhí)行一定次數(shù)進行一次Undo物理頁面清理工作,導(dǎo)致了查詢trx_rseg_history_len無法歸0。

5.2 trx_rseg_history_len !=0 有啥影響嗎?

歷史數(shù)據(jù)的purge工作已經(jīng)完成,保證了數(shù)據(jù)正確性。只是Undo物理頁面滯后清理,后果是無用數(shù)據(jù)占用磁盤久一點,但是換取了較少的I/O??!

5.3 為什么MySQL實例啟動立即查詢,trx_rseg_history_len !=0?

MySQL在啟動時,會執(zhí)行一些語句(具體內(nèi)容還不清楚,參考scripts目錄,該目錄內(nèi)容在實例初始化時會被執(zhí)行),產(chǎn)生Undo信息。

5.4 暫時沒有被清理的Undo頁面怎么辦?

Purge線程再次激活、trx_purge滿rseg_truncate_frequency次時會清理的;
MySQL實例shutdown時,會清理Undo表空間。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容