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

1. 寫在前面

經過上一篇文章的介紹,我們已經對 commit 階段有了整體的認識。

這篇文章,我們一起進入各子階段,看看它們都會幹點什麼,以及會怎麼幹。

爲了方便理解,我們假設有 30 個事務,它們對應的用戶線程編號也從 1 到 30。

2. flush 子階段

用戶線程 16 加入 flush 隊列,成爲 flush 隊長,並且通過申請獲得 LOCK_log 互斥量。

flush 隊長收編用戶線程 17 ~ 30 作爲它的隊員,隊員們進入 flush 隊列之後,就開始等待,收到 commit 子階段的隊長髮來的通知纔會結束等待。

flush 隊長開始幹活之前,會帶領它的隊員從 flush 隊列挪出來,給後面進入二階段提交的其它事務騰出空間。

從 flush 隊列挪出來之後,flush 隊長會觸發操作系統,把截止目前產生的所有 redo 日誌都刷盤。

這些 redo 日誌,當然就包含了它和隊員們在 prepare 階段及之前產生的所有 redo 日誌了。

觸發 redo 日誌刷盤之後,flush 隊長會從它自己開始,把它和隊員們產生的 binlog 日誌寫入 binlog 日誌文件。

以隊長爲例,寫入過程是這樣的:

  • 從事務對應的 trx_cache 中把 binlog 日誌讀出來,存放到 trx_cache 的內存 buffer 中。<br />每次讀取 4096(對應代碼裏的 IO_SIZE)字節的 binlog 日誌,最後一次讀取剩餘的 binlog 日誌(小於或等於 4096 字節)。
  • 把 trx_cache 內存 buffer 中的 binlog 日誌寫入 binlog 日誌文件。

隊員們產生的 binlog 日誌寫入 binlog 日誌文件的過程,和隊長一樣。隊長把自己和所有隊員產生的 binlog 日誌都寫入 binlog 日誌文件之後,flush 子階段的活就幹完了。

flush 隊長寫完 binlog 日誌之後,如果發現 binlog 日誌文件的大小大於等於系統變量 max_binlog_size 的值(默認爲 1G),會設置一個標誌(rotate = true),表示需要切換 binlog 日誌文件。後面 commit 子階段會用到。

到這裏,用戶線程 16 作爲隊長的 flush 子階段,就結束了。

3. sync 子階段

爲了劇情需要,我們假設用戶線程 6 ~ 15 此刻還在 sync 隊列中,用戶線程 6 最先進入隊列,是 sync 隊長,用戶線程 7 ~ 15 都是隊員。

用戶線程 16(flush 隊長)帶領隊員們來到 sync 子階段,發現 sync 隊列中已經有先行者了。

有點遺憾,用戶線程 16 不能成爲 sync 子階段的隊長,它和隊員們都會變成 sync 子階段的隊員。

此時,用戶線程 6 是 sync 隊長,用戶線程 7 ~ 30 是隊員。

進入 sync 子階段之後,用戶線程 16(flush 隊長)會釋放它在 flush 子階段獲得的 LOCK_log 互斥量,flush 子階段下一屇的隊長就可以獲得 LOCK_log 互斥量開始幹活了。

交待完用戶線程 16(flush 隊長)在 sync 子階段要乾的活,該說說 sync 隊長了。

sync 隊長會申請 LOCK_sync 互斥量,獲得互斥量之後,就開始準備給自己和隊員們幹 sync 子階段的活了。

隊員們依然在一旁當喫瓜羣衆,等待 sync 隊長給它們幹活。它們會一直等待,收到 commit 子階段的隊長髮來的通知纔會結束等待。

就在 sync 隊長準備甩開膀子大幹一場時,它發現前面還有一個關卡:本次組提交能不能觸發操作系統把 binlog 日誌刷盤

sync 隊長怎麼知道自己能不能過這一關?

它會查看一個計數器的值(sync_counter),如果 sync_counter + 1 大於等於系統變量 sync_binlog 的值,就說明自己可以過關。

否則,不能通過這一關,用戶線程 6 作爲隊長的 sync 子階段就到此結束了,它什麼都不用幹。

如果 sync 隊長過關了,它會想:好不容易過關,我要再收編一些隊員,纔不枉費我的好運氣。

sync 隊長會帶領隊員們繼續在 sync 隊列中等待,以收編更多隊員。這個等待過程是有期限的,滿足以下兩個條件之一,就結束等待:

  • 已經等待了系統變量 binlog_group_commit_sync_delay 指定的時間(單位:微妙),默認值爲 0。
  • sync 隊列中的用戶線程數量(sync 隊長和所有隊員加在一起)達到了系統變量 binlog_group_commit_sync_no_delay_count 的值,默認值爲 0。

等待結束之後,sync 隊長會帶領隊員們從 sync 隊列挪出來,給後面進入二階段提交的其它事務騰出空間。

接下來,sync 隊長終於可以大幹一場了,它會觸發操作系統把 binlog 日誌刷盤,確保它和隊員們產生的 binlog 日誌寫入到磁盤上的 binlog 日誌文件中。

這樣即使服務器突然異常關機,binlog 日誌也不會丟失了。

刷盤完成之後,用戶線程 6 作爲隊長的 sync 子階段,就到此結束。

介紹完 sync 子階段的主要流程,我們再來說說 sync_counter

sync_counter 的值從 0 開始,某一次組提交的 sync 隊長沒有過關,不會觸發操作系統把 binlog 日誌刷盤,sync_counter 就加 1。

sync_counter 會一直累加,直到後續的某一次組提交,sync_counter + 1 大於等於系統變量 sync_binlog 的值,sync 隊長會把 sync_counter 重置爲 0,並且觸發操作系統把 binlog 日誌刷盤。

sync_counter 重置爲 0 之後,sync 子階段是否要觸發操作系統把 binlog 日誌刷盤,又會開始一個新的輪迴。

4. commit 子階段

同樣,爲了劇情需要,我們假設用戶線程 1 ~ 5 此刻還在 commit 隊列中,用戶線程 1 最先進入隊列,是 commit 隊長,用戶線程 2 ~ 5 都是隊員。

用戶線程 6(sync 隊長)帶領隊員們來到 commit 子階段,發現 commit 隊列中也已經有先行者了。

用戶線程 6 和隊員們一起,都變成了 commit 子階段的隊員。

此刻,用戶線程 1 是 commit 隊長,用戶線程 2 ~ 30 是隊員。

進入 commit 子階段之後,用戶線程 6(sync 隊長)會釋放它在 sync 子階段獲得的 LOCK_sync 互斥量,sync 子階段下一屇的隊長就可以獲得 LOCK_sync 互斥量開始幹活了。

交待完用戶線程 6(sync 隊長)進入 commit 子階段要乾的活,該說說 commit 隊長了。

commit 隊長會申請 LOCK_commit 互斥量,獲得互斥量之後,根據系統變量 binlog_order_commits 的值決定接下來的活要怎麼幹。

如果 binlog_order_commits = true,commit 隊長會把它和隊員們的 InnoDB 事務逐個提交,然後釋放 LOCK_commit 互斥量。

提交 InnoDB 事務完成之後,commit 隊長會通知它的隊員們(用戶線程 2 ~ 30):所有活都幹完了,你們都散了吧,別圍觀了,該幹啥幹啥去。

隊員們收到通知之後,作鳥獸散,它們的二階段提交也都結束了。

如果 binlog_order_commits = false,commit 隊長不會幫助隊員們提交 InnoDB 事務,它提交自己的 InnoDB 事務之後,就會釋放 LOCK_commit 互斥量。

然後,通知所有隊員(用戶線程 2 ~ 30):flush 子階段、sync 子階段的活都幹完了,你們自己去提交 InnoDB 事務。

隊員們收到通知之後,就各自提交自己的 InnoDB 事務,誰提交完成,誰的二階段提交就結束了。

最後,commit 隊長還要處理最後一件事。

如果用戶線程 16(flush 隊長)把 rotate 設置爲 true 了,說明 binlog 日誌文件已經達到了系統變量 max_binlog_size 指定的上限,需要切換 binlog 日誌文件。

切換指的是關閉 flush 子階段剛寫入的 binlog 日誌文件,創建新的 binlog 日誌文件,以供後續事務提交時寫入。

如果需要切換 binlog 日誌文件,切換之後,還會根據系統變量 binlog_expire_logs_auto_purgebinlog_expire_logs_secondsexpire_logs_days 清理過期的 binlog 日誌。

處理完切換 binlog 日誌文件的邏輯之後,commit 隊長的工作就此結束,它的二階段提交就完成了。

5. 總結

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

sync 子階段,如果 sync_counter + 1 大於等於系統變量 max_binlog_size 的值,sync 隊長會把 binlog 日誌刷盤。

commit 子階段,如果系統變量 binlog_order_commits 的值爲 true,commit 隊長會把自己和隊員們的 InnoDB 事務都提交,否則,commit 隊長和隊員各自提交自己的 InnoDB 事務。

本期問題:commit 子階段這種清理過期 binlog 日誌的邏輯,會有什麼問題嗎?歡迎留言交流。

下期預告:MySQL 核心模塊揭祕 | 10 期 | binlog 怎麼寫入日誌文件?

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