MySQL 核心模塊揭祕 | 11 期 | InnoDB 提交事務,提交了什麼?

二階段提交 commit 階段的 commit 子階段,InnoDB 存儲引擎層面提交事務,主要是做一些收尾工作,這些有收尾工作有哪些?

作者:操盛春,愛可生技術專家,公衆號『一樹一溪』作者,專注於研究 MySQL 和 OceanBase 源碼。

愛可生開源社區出品,原創內容未經授權不得隨意使用,轉載請聯繫小編並註明來源。

1. 關於緩存 undo 段

爲了提升分配 undo 段的效率,事務提交過程中,InnoDB 會緩存一些 undo 段。

只要同時滿足兩個條件,insert undo 段或 update undo 段就能被緩存。

條件 1:undo 段中只有一個 undo 頁。

條件 2:這個唯一的 undo 頁中,已經使用的的空間必須小於數據頁大小的四分之三。以默認大小 16K 的 undo 頁爲例,undo 頁中已經使用的空間必須小於 12K。

如果 insert undo 段滿足緩存條件,它會加入回滾段的 insert_undo_cached 鏈表頭部。

如果 update undo 段滿足緩存條件,它會加入回滾段的 update_undo_cached 鏈表頭部。

2. InnoDB 提交事務

二階段提交過程中,commit 階段的 flush 子階段,把 prepare 階段及之前產生的 redo 日誌都刷盤了,把事務執行過程中產生的 binlog 日誌都寫入 binlog 日誌文件了。

sync 子階段根據系統變量 sync_binlog 的值決定是否觸發操作系統把 binlog 日誌刷盤。

前兩個子階段,都只處理了日誌,不涉及 InnoDB 的事務。這兩個階段完成之後,InnoDB 的事務還沒有提交,事務還處於準備提交狀態(TRX_STATE_PREPARED)。

commit 子階段纔會真正提交 InnoDB 的事務,這個階段完成之後,事務就提交完成了。

commit 子階段提交 InnoDB 的事務,要做的事情有這些:

  • 修改 insert undo 段的狀態。
  • 生成事務提交號,用於 purge 線程判斷是否能清理某些 update undo 日誌組中的 undo 日誌。
  • 修改 update undo 段的狀態。
  • 把 update undo 段中的 undo 日誌組加入回滾段的 history list 鏈表。purge 線程會從這個鏈表中獲取需要清理的 update undo 日誌組。
  • 把事務狀態修改爲 TRX_STATE_COMMITTED_IN_MEMORY
  • 釋放事務執行過程中 InnoDB 給表或記錄加的鎖。
  • 重新初始化事務對象,以備當前線程後續使用。

2.1 修改 insert undo 段狀態

如果事務插入記錄到用戶普通表,InnoDB 會爲事務分配一個 insert undo 段。

如果事務插入記錄到用戶臨時表,InnoDB 會爲事務分配另一個 insert undo 段。

InnoDB 可能會給事務分配 0 ~ 2 個 insert undo 段。commit 子階段會修分配給事務的所有 insert undo 段的狀態。

如果 insert undo 段滿足緩存條件,它的狀態會被修改爲 TRX_UNDO_CACHED,否則,它的狀態會被修改爲 TRX_UNDO_TO_FREE

事務提交完成之後,InnoDB 會根據狀態緩存或者釋放 insert undo 段。

2.2 生成事務提交號

事務提交號是事務對象的 no 屬性,通常用 trx->no 表示。

代碼裏,對事務提交號的註釋是 transaction serialization number,直譯成中文應該稱爲事務序列號,或者事務串行號。

因爲 trx->no 是在事務提交時生成的,我們還是把它稱爲事務提交號更容易理解一些。

只有 update undo 段需要事務提交號。purge 線程清理 update undo 日誌時,會根據 update undo 段的 undo 日誌組中保存的事務提交號,決定是否能清理這個 undo 日誌組中的 undo 日誌。

修改 update undo 段的狀態之前,InnoDB 會生成事務提交號,保存到事務對象的 no 屬性中。

// storage/innobase/trx/trx0trx.cc
static inline bool trx_add_to_serialisation_list(trx_t *trx) {
  ...
  trx->no = trx_sys_allocate_trx_no();
  ...
}

trx_sys_allocate_trx_no() 調用 trx_sys_allocate_trx_id_or_no() 生成事務提交號。

// storage/innobase/include/trx0sys.ic
// 生成事務 ID
inline trx_id_t trx_sys_allocate_trx_id() {
  ut_ad(trx_sys_mutex_own());
  return trx_sys_allocate_trx_id_or_no();
}
// 生成事務提交號
inline trx_id_t trx_sys_allocate_trx_no() {
  ut_ad(trx_sys_serialisation_mutex_own());
  return trx_sys_allocate_trx_id_or_no();
}

從上面的代碼可以看到,生成事務 ID 和事務提交號調用的是同一個方法,trx_sys_allocate_trx_id_or_no() 的代碼如下:

// storage/innobase/include/trx0sys.ic
inline trx_id_t trx_sys_allocate_trx_id_or_no() {
  ...
  // trx_sys_allocate_trx_id_or_no() 每次被調用
  // trx_sys->next_trx_id_or_no 加 1
  // trx_id 保存的是加 1 之前的值
  trx_id_t trx_id = trx_sys->next_trx_id_or_no.fetch_add(1);
  ...
  return trx_id;
}

trx_sys->next_trx_id_or_no 保存的是下一個事務 ID 或事務提交號,具體是哪個,取決於是生成事務 ID 還是生成事務提交號先調用 trx_sys_allocate_trx_id_or_no()

也就是說,事務 ID 和事務提交號是同一條流水線上生產出來的。我們以 trx 1 和 trx 2 兩個事務爲例,來說明生成事務 ID 和事務提交號的流程。

假設此時 trx_sys->next_trx_id_or_no 的值爲 100,trx 1、trx 2 啓動和提交的順序如下:

  • trx 1 啓動。
  • trx 2 啓動。
  • trx 1 提交。
  • trx 2 提交。

其於以上假設,生成事務 ID 和事務提交號的流程如下:

  • trx 1 生成事務 ID,得到 100。trx_sys->next_trx_id_or_no 加 1,結果爲 101。
  • trx 2 生成事務 ID,得到 101。trx_sys->next_trx_id_or_no 加 1,結果爲 102。
  • trx 1 生成事務提交號,得到 102。trx_sys->next_trx_id_or_no 加 1,結果爲 103。
  • trx 2 生成事務提交號,得到 103。trx_sys->next_trx_id_or_no 加 1,結果爲 104。

從以上流程可以看到,事務 ID 和事務提交號都來源於 trx_sys->next_trx_id_or_no,相互之間不會重複。

2.3 修改 update undo 段狀態

如果事務更新或刪除了用戶普通表的記錄,InnoDB 會爲事務分配一個 update undo 段。

如果事務更新或刪除了用戶臨時表的記錄,InnoDB 會爲事務分配另一個 update undo 段。

InnoDB 可能會給事務分配 0 ~ 2 個 update undo 段。commit 子階段會修改分配給事務的所有 update undo 段的狀態。

如果 update undo 段滿足緩存條件,它的狀態會被修改爲 TRX_UNDO_CACHED,否則,它的狀態會被修改爲 TRX_UNDO_TO_PURGE

2.4 undo 日誌組加入 history list

修改完 update undo 段的狀態,update undo 段的 undo 日誌組會加入回滾段的 history list 鏈表。purge 線程會從這個鏈表中獲取要清理的 undo 日誌組。

前面已經生成了事務提交號,這裏會把事務提交號寫入 undo 日誌組的頭信息中。

如果 update undo 段的狀態爲 TRX_UNDO_CACHED,表示這個 undo 段需要緩存起來。它會加入回滾段的 update_undo_cached 鏈表頭部,以備後續其它事務需要 update undo 段時,能夠快速分配。

3. InnoDB 提交事務完成

前面的一系列操作完成之後,InnoDB 提交事務的操作就完成了。

現在,要把事務狀態修改爲 TRX_STATE_COMMITTED_IN_MEMORY

修改之後,新啓動的事務就能看到該事務插入或更新的記錄,看不到當前事務刪除的記錄。

接下來,InnoDB 會釋放事務執行過程中加的表鎖、記錄鎖。

釋放鎖之後,還要處理 insert undo 段。

如果 insert undo 段的狀態爲 TRX_UNDO_CACHED,表示這個 undo 段需要緩存起來。它會加入回滾段的 insert_undo_cached 鏈表頭部,以備後續其它事物需要 insert undo 段時,能夠快速分配。

如果 insert undo 段的狀態爲 TRX_UNDO_TO_FREE,它會被釋放,佔用的 undo 頁會還給 undo 表空間。

二階段提交的 flush 子階段,已經把 prepare 階段及之前產生的 redo 日誌都刷盤了。

commit 子階段,修改 insert undo 段和 update undo 段的狀態,還會產生 redo 日誌。

InnoDB 不會主動觸發操作系統把這些 redo 日誌刷盤,而是由操作系統決定什麼時候把這些 redo 日誌刷盤。

InnoDB 敢這麼做,是因爲這些 redo 日誌對於確定事務狀態已經不重要了。即使這些 redo 日誌刷盤之前,服務器突然異常關機,導致 undo 段的狀態丟失。MySQL 下次啓動時,也能正確的識別到事務已經提交完成了。

4. 重新初始化事務對象

到這裏,InnoDB 提交事務該做的操作都已經做完了。提交事務完成之後,該做的事也都做了。

對於上一個事務,事務對象的使命已經結束。這裏會把事務狀態修改爲 TRX_STATE_NOT_STARTED

事務對象也會被重新初始化,但是它不會被釋放。也就是說,事務對象不會回到事務池中,而是留給當前連接後續啓動新事務時複用。

5. 總結

InnoDB 提交事務,就像我們填完一個表格之後,最後蓋上的那個戳,總體上來說,要幹 3 件事。

第 1 件,修改分配給事務的各 undo 段的狀態。

如果數據庫發生崩潰,重新啓動後,undo 段的狀態是影響事務提交還是回滾的因素之一。

第 2 件,修改事務對象的狀態。

如果數據據庫一直運行,不發生崩潰,就靠事務對象的狀態來標識事務是否已提交。

第 3 件,把各 undo 段中的 undo 日誌組加入 history list 鏈表。

其它事務都不再需要使用這些 undo 日誌時,後臺 purge 線程會清理這些 undo 日誌組中的日誌。

本期問題:關於本期內容,如有問題,歡迎留言交流。

下期預告:MySQL 核心模塊揭祕 | 12 期 | 創建 savepoint

更多技術文章,請訪問:https://opensource.actionsky.com/

關於 SQLE

SQLE 是一款全方位的 SQL 質量管理平臺,覆蓋開發至生產環境的 SQL 審覈和管理。支持主流的開源、商業、國產數據庫,爲開發和運維提供流程自動化能力,提升上線效率,提高數據質量。

SQLE 獲取

類型 地址
版本庫 https://github.com/actiontech/sqle
文檔 https://actiontech.github.io/sqle-docs/
發佈信息 https://github.com/actiontech/sqle/releases
數據審覈插件開發文檔 https://actiontech.github.io/sqle-docs/docs/dev-manual/plugins/howtouse
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章