MySQL 核心模塊揭祕 | 08 期 | 二階段提交 (2) commit 階段

這篇文章是二階段提交的 commit 子階段的前奏,聊聊 commit 子階段相關的一些概念。

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

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

本文基於 MySQL 8.0.32 源碼,存儲引擎爲 InnoDB。

1. 刷盤這件事

操作系統爲文件提供了緩衝區,稱爲 page cache

程序寫入內容到磁盤文件,實際上是先寫入 page cache,再由操作系統把 page cache 中的內容寫入磁盤文件。

把 page cache 中的內容寫入磁盤文件這個操作,我們通常口語化的稱爲刷盤

刷盤有兩種方式:

  • 一種是操作系統的後臺線程異步刷盤。
  • 另一種是程序主動觸發操作系統的同步刷盤。

2. commit 階段

二階段提交的 commit 階段,分爲三個子階段。

flush 子階段,要幹兩件事:

第 1 件,觸發操作系統把 prepare 階段及之前產生的 redo 日誌刷盤。

事務執行過程中,改變(插入、更新、刪除)表中數據產生的 redo 日誌、prepare 階段修改 undo 段狀態產生的 redo 日誌,都會由後臺線程先寫入 page cache,再由操作系統把 page cache 中的 redo 日誌刷盤。

等待操作系統把 page cache 中的 redo 日誌刷盤,這個時間存在不確定性,InnoDB 會在需要時主動觸發操作系統馬上把 page cache 中的 redo 日誌刷盤。

上一篇文章,我們介紹過,二階段提交的 prepare 階段不會主動觸發操作系統把 page cache 中的 redo 日誌刷盤。這個刷盤操作會留到 flush 子階段進行。

第 2 件,把事務執行過程中產生的 binlog 日誌寫入 binlog 日誌文件。

這個寫入操作,也是先寫入 page cache,至於操作系統什麼時候把 page cache 中的 binlog 日誌刷盤,flush 子階段就不管了。

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

commit 子階段,完成 InnoDB 的事務提交。

3. 組提交

flush 子階段會觸發 redo 日誌刷盤,sync 子階段可能會觸發 binlog 日誌刷盤,都涉及到磁盤 IO。

TP 場景,比較常見的情況是事務只改變(插入、更新、刪除)表中少量數據,產生的 redo 日誌、binlog 日誌也比較少。

我們把這種事務稱爲小事務

以 redo 日誌爲例,一個事務產生的 redo 日誌少,操作系統的一個頁就有可能存放多個事務產生的 redo 日誌。

如果每個事務提交時都把自己產生的 redo 日誌刷盤,共享操作系統同一個頁存放 redo 日誌的多個事務,就會觸發操作系統把這個頁多次刷盤。

數據庫閒的時候,把操作系統的同一個頁多次刷盤,也沒啥問題,反正磁盤閒着也是閒着。

數據庫忙的時候,假設某個時間點有 1 萬個小事務要提交,每 10 個小事務共享操作系統的一個頁用於存放 redo 日誌,總共需要操作系統的 1000 個頁。

1 萬個事務各自提交,就要觸發操作系統把這 1000 個數據頁刷盤 10000 次。

根據上面假設的這個場景,我們可以看到,這些事務都在某個時間點提交,可以等到共享操作系統同一個頁的事務把 redo 日誌都寫入到 page cache 之後,再觸發操作系統把 page cache 的這一個頁刷盤。

這樣一來,1000 個數據頁,只刷盤 1000 次就可以了,刷盤次數只有原來的十分之一,效率大大的提高了。

上面是以 redo 日誌爲例描述操作系統的同一個頁重複刷盤的問題,binlog 日誌也有同樣的問題。

某個時間點提交的多個事務觸發操作系統的同一個頁重複刷盤,這是個問題,爲了解決這個問題,InnoDB 引入了組提交

4. 隊列和互斥量

組提交,就是把一組事務攢到一起提交,InnoDB 使用隊列把多個事務攢到一起。

commit 階段的 3 個子階段都有自己的隊列,分別爲 flush 隊列、sync 隊列、commit 隊列。

每個隊列都會選出一個隊長,負責管理這個隊列,選隊長的規則很簡單,先到先得。

對於每個隊列,第一個加入該隊列的用戶線程就是隊長,第二個及以後加入該隊列的都是隊員

代碼裏把隊長稱爲 leader,隊員稱爲 follower

我們生活中,不管當什麼級別的隊長,總會有點什麼不一樣,比如:有點錢、有點權、有點面子。

當選隊長的用戶線程,會有什麼不一樣嗎?

這個當然是有的,隊長有個特權,就是多幹活

每個子階段的隊長,都會把自己和所有隊員在對應子階段要乾的事全都幹了。隊員只需要在旁邊當喫瓜羣衆就好。

commit 子階段有點不一樣,到時候會介紹。

以 flush 子階段爲例,我們假設 flush 隊列的隊長爲 A 隊長,A 隊長收編一些隊員之後,它會帶領這幫隊員從 flush 隊列挪走,並且開始給自己和所有隊員幹活,隊員們就在一旁當喫瓜羣衆。

A 隊長帶領它的隊員挪走之後,flush 隊列就變成空隊列了。

接下來第一個進入 flush 隊列的用戶線程,又成爲下一組的隊長,我們稱它爲 B 隊長

A 隊長正在幹活,還沒幹完呢。B 隊長收編了一些隊員之後,也帶領這幫隊員從 flush 子階段的隊列挪走,並且也要開始給自己和所有隊員幹活了。

如果 A 隊長和 B 隊長都把自己和各自隊員產生的 binlog 日誌寫入 binlog 日誌文件,相互交叉寫入,那是會出亂子的。

爲了避免 flush 子階段出現兩個隊長同時幹活導致出亂子,InnoDB 給 flush 子階段引入了一個互斥量,名字是 LOCK_log

sync 子階段、commit 子階段也需要避免出現多個隊長同時幹活的情況,這兩個子階段也有各自的互斥量,分別是 LOCK_syncLOCK_commit

5. 總結

二階段提交的 commit 階段分爲三個子階段:flush 子階段、sync 子階段、commit 子階段。

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

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

爲了避免每個事務各自提交,觸發操作系統對同一個頁頻繁的重複刷盤,InnoDB 引入了組提交。

爲了避免每個子階段出現多個隊長同時幹活的情況,InnoDB 還引入了三個互斥量:LOCK_log、LOCK_sync、LOCK_commit。

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

下期預告:MySQL 核心模塊揭祕 | 09 期 | 二階段提交 (3) flush、sync、commit 子階段。

更多技術文章,請訪問: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
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章