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_purge
、binlog_expire_logs_seconds
、expire_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 審覈和管理。支持主流的開源、商業、國產數據庫,爲開發和運維提供流程自動化能力,提升上線效率,提高數據質量。