MYSQL中binlog優化的一些思考彙總
問題
問題1:如何解決事務提交時flush redo log帶來的效能損失
WAL是實現事務永續性(D)的一個常用技術,基本原理是將事務的修改記錄redo log。redo log順序追加寫入。事務提交時,只需要保證事務的redo log落盤即可,通過redo log的順序寫代替頁面的隨機寫提升資料庫系統的效能。但是,該方案必須要求每個事務提交時都將其生成的redo log進行一次刷盤,效率不高。
問題2:binlog和引擎層事務提交的順序問題
對於單個事務而言,日誌寫入順序是先redo log再binlog,只要維持該順序即可維持正確性。但對於一個高併發的資料庫系統而言,每時每刻可能都會存在眾多併發執行的事務。我們還需要通過一定的手段來維護Server層binlog和引擎層事務提交的順序一致性。
維護這種順序一致性其實是為了保證備份工具Xtrabackup的正確性。
當 binlog 作為協調者,如果其中記錄的事務順序和儲存引擎層記錄的順序不一樣的話,備份工具(Innodb Hot Backup)拿到備份集的位點可能會存在空洞。因為備份工具會拷貝 redo 日誌,在 redo 的頭部會記錄最後一個提交的事務對應的 binlog 位點,備份集建立之後就會根據這個位點繼續從主庫 dump binlog。
假如有三個事務 T1,T2,T3 已經 fsync 到 binlog 檔案中,三個事務的在檔案中的位點分別是 100,200,300,但是在引擎層的只有 T1 和 T3 完成了 commit 並記錄到 redo 中,最後一個 commit 的事務 T3 位點是 300。此時通過備份工具拿到的資料就是這樣的狀態,備份集啟動的時候會走崩潰恢復的流程,prepare 事務被回滾(備份集不會備份 binlog 檔案,對應上個小節 xid 集合為空),自位點 300 繼續從主庫同步binlog並apply,導致 T2 在備庫就丟失了。
因此,我們必須設計一種機制來保證Server層的binlog寫入順序和儲存引擎層的事務提交順序保持一致。
問題3:同時寫redo和binlog帶來的效能下降
問題1中提到每次的事務提交會帶來效能問題,而這個問題在引入binlog後會變得更加嚴重。每個事務提交都會增加一次檔案IO,且需要刷盤。如果系統併發比較高,那麼這些IO將會成為拖慢整體效能的瓶頸。
解決方案
問題1:Redo log組提交技術
redo組提交技術思想很簡單:通過將多個事務redo log的刷盤動作合併,減少刷盤次數。Innodb的日誌系統裡面,每條redo log都有一個LSN(Log Sequence Number)。事務將日誌拷貝到redo log buffer時,都會獲取當前最大的LSN,且LSN單調遞增,因此可以保證不同事務的LSN不會重複。那麼假設三個事務Trx1、Trx2、Trx3的日誌的最大LSN分別為LSN1、LSN2、LSN3(LSN1 < LSN2 < LSN3),它們同時進行提交,那麼如果trx3率先執行提交,它會要求刷盤至LSN3處,這樣就順便將Trx1、Trx2的redo log也刷了,Trx1和Trx2會判斷自己的LSN小於當前已落盤的最大LSN,就無需再次刷盤。
問題2:內部XA事務
開啟binlog情況下,引入內部XA事務來協調上層和儲存引擎層,具體來說,在事務提交時引入兩個階段:
prepare:將redo log刷盤操作以確保data頁和undo頁的更新已經重新整理到磁碟,設定事務狀態為PREPARE狀態;
commit:1). 寫binlog並刷盤,2).呼叫引擎層事務提交介面。將事務狀態設定為COMMIT。
如此兩階段提交主要是要保證資料庫崩潰時的正確性。因為一旦binlog落盤了,它就可能被下游節點消費。這種事務必須在重啟後被commit而非rollback。而對於binlog未落盤的事務,崩潰恢復時直接回滾。
具體來說,故障恢復時,掃描最後一個binlog檔案(在flush階段,如果binlog大小超過閥值,進行rotate binlog檔案,會保證該檔案記錄的最後一個事務一定被提交),提取其中的xid。重做檢查點以後的redo日誌,讀取事務的undo段資訊,蒐集處於prepare階段的事務列表,將事務的xid與binlog中記錄的xid對比,若存在,則提交,否則就回滾。
MySQL5.6以前,為了保證資料庫binlog的寫入順序和InnoDB層的事務提交順序一致,MySQL資料庫內部使用了prepare_commit_mutex鎖。
具體來說,在兩階段提交引擎層 prepare 的時候加鎖,在引擎層 commit 之後釋放鎖:
innobase_xa_prepare() write() and fsync() binary log innobase_commit()
這樣確實可以保證 binlog 和 innodb 的事務順序一致,但是這把鎖會導致所有的事務序列化執行,且每次提交都會至少呼叫多次fsync,效率很低。這也是接下來需要探討並解決的一個問題。
問題4
參考redo log優化技術,引入組提交技術來優化binlog的寫入效能。
考慮未優化時事務提交流程:
prepare:該階段刷儲存引擎層(innodb)的redo log並將事務狀態設定為PREPARED(更新undo page上事務狀態),該階段不涉及binlog
commit:寫binlog日誌並刷盤,同時引擎層釋放鎖,釋放回滾段、設定事務狀態為COMMITTED等
所謂的組提交技術其本質上是將耗時的commit步驟進行更細粒度的拆分,具體來說:
將步驟2的commit 分為三個階段:
Flush:寫binlog,但不sync
Sync: 呼叫 fsync 操作將檔案落盤
Commit :呼叫儲存引擎介面提交事務
這裡的fsync是耗時操作,因此我們希望能攢足夠多的寫入後才進行一次fsync呼叫,在這裡使用batch技術。其原理是:上述步驟中的每個階段都有一個對應的任務連結串列,每個進入該階段的執行緒會將自己的任務加入至該連結串列中,連結串列加鎖以保證正確性。第一個加入該連結串列的執行緒會成為Leader,後續的執行緒成為Follower。連結串列中的所有任務組成一個Batch,由Leader負責執行,而Follower則等待其任務完成即可。
一旦某階段的連結串列任務執行完成,這些任務會進入下一個階段,同樣加入該階段的任務連結串列,重複上述執行流。
如此設計有以下幾點好處:
- 使用Leader執行而非每個執行緒各自執行可有效減少write/fsync等呼叫次數,提高效率
- 可保證事務寫binlog和引擎層提交的順序一致
- 多事務可併發執行,而不再需要被prepare_commit_mutex鎖強制序列化
除此之外,MYSQL還對prepare階段刷redo log進行了進一步優化。原來的設計是多事務可併發地刷redo log,同樣效率不夠高。可以將prepare階段的redo log刷盤放在commit階段的Flush階段執行。但有個小問題需要說明的是:優化前每個執行緒各自負責自己的redo log的落盤,且知道需要flush的redo log的lsn,如果改為在Flush階段由其Leader執行緒統一落盤,此時它不瞭解每個執行緒的redo log的lsn,因此它簡單粗暴地flush至log_sys的最大lsn,這就保證了要提交事務的redo log一定可以被落盤。
總結
到此這篇關於MYSQL中binlog優化思考的文章就介紹到這了,更多相關MYSQL binlog優化思考內容請搜尋我們以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援我們!