innodb二階段日志提交機制和組提交解析


前些天在查看關於innodb_flush_log_at_trx_commit的官網解釋時產生了一些疑問,關於innodb_flush_log_at_trx_commit參數的詳細解釋參見官網:

https://dev.mysql.com/doc/refman/5.7/en/innodb-parameters.html#sysvar_innodb_flush_log_at_trx_commit

其中有一段是這么寫的:
With a value of 2, the contents of the InnoDB log buffer are written to the log file after each transaction commit and the log file is flushed to disk approximately once per second.
意思是:如果innodb_flush_log_at_trx_commit的值設為2,那么log buffer里的內容會在每次提交時被寫入redo log file,然后redo log file每秒被flush到disk。
由於innodb的redo log file據我所知是在硬盤上的ib_logfile,所以對於這里的log file被flush到disk很疑惑,難道log buffer和disk之間還存在了一層可以緩存log file的結構?
在查閱了大量中英文資料后,總算有了初步的了解,暫總結於此。
相關參考資料:
 
一、名詞解釋
在innodb存儲引擎中,有一種獨有的log file,即redo log file,因此對於innodb存儲引擎來說,就存在兩種logfile:redo log和binlog.
redo log:即data目錄下的ib_logfile0,ib_logfile1(個數由innodb_log_files_in_group控制),innodb存儲引擎特有的redo,在內存中有相應的redo log buffer。
因此寫redo時的3層結構為:redo log buffer--->文件系統緩存中的redo logfile--->disk上的redo log file
binlog:默認在data目錄下,也可以通過log_bin參數直接指定路徑,文件名為默認為<hostname>-bin前綴的文件,在內存中沒有log buffer。
因此寫binlog時的3層結構為:binlog_cache-->文件系統緩存中的binlog--->disk上的binlog
 
二、二階段日志寫的流程
為確保innodb的redo與MySQL的binlog一致,innodb的事務提交采用了two-phase commit的二階段提交機制。
所謂二階段就是指server層寫binlog和innodb層寫redo的階段。
使用Innodb引擎並開啟binlog后,如果會話發出了commit的請求,那么在committed之前,一系列的流程為:
1.prepare階段:
此階段負責:
在Innodb層獲取獨占模式的prepare_commit_mutex,將事務的trx_id寫入redo log(redo日志的寫機制為WAL所以在事務修改前就會寫redo buffer而不是commit時一次性寫入)。
2.commit階段:
2.1:第一步,寫binlog
此階段調用兩個方法write()和fsync(),前者負責將binlog從binlog cache寫入文件系統緩存,后者負責將文件系統緩存中的binlog寫入disk,后者的調用機制是由sync_binlog參數控制的。
關於sync_binlog參數:
  • sync_binlog=0:表示fsync()的調用完全交給操作系統,即文件系統緩存中的binlog是否刷新到disk完全由操作系統控制。
  • sync_binlog=1:表示在發出事務提交請求時,binlog一定會被固化到disk,write()跳過文件系統緩存直接寫入disk。
  • sync_binlog=N(N>1):數據庫崩潰時,可能會丟失N-1個事務。
注意binlog也是有cache的,在事務執行過程中生成的binlog會被存儲在binlog cache中,此cache大小由binlog_cache_size,這個size是session級別的,即每個會話都有一個binlog cache。
2.2:第二步,innodb進行commit
在Innodb層寫入commit flag,調用write和fsync將commit信息的redo寫入磁盤,然后釋放prepare_commit_mutex
引擎層將redo log buffer中的redo寫入文件系統緩存(write),然后將文件系統緩存中的redo log寫入disk(fsync),寫入機制取決於innodb_flush_log_at_trx_commit參數。
innodb_flush_log_at_trx_commit:(默認值為1)
  • 此值為0表示:redo log buffer的內容每秒會被寫入文件系統緩存的redo log里,同時被flush(固化)到disk上的redo log file中。
  • 此值為1表示:redo log buffer的內容會在事務commit時被寫入文件系統緩存的redo log里,同時被flush(固化)到disk上的redo log file中。
  • 此值為2表示:redo log buffer的內容會在事務commit時被寫入文件系統緩存的redo log里,而文件系統緩存的redo log每秒一次被flush(固化)到disk上的redo log file中。

注意redo和undo是在事務執行過程中就即時生成的,且早於數據庫真正被修改,這被稱作write ahead logging(WAL),undo的disk文件位置默認在系統表空間中,5.6以后也可以指定獨立的undo表空間。

至此完成事務提交,清除會話undo信息,將事務設置為TRX_NOT_STARTED狀態。
 
三、故障恢復解讀
Innodb進行crash recovery時是根據binlog來進行前滾回滾的,只有記錄了binlog才會根據redo log前滾或回滾事務。
crash recovery的流程其實是:先掃描binlog,提取出xid,然后比較redo中checkpoint之后的xid,如果在binlog存在,那么提交,如果不存在那么回滾。
二階段日志提交其實是依靠一種內部的分布式(XA)機制避免的,因此MySQL的innodb_support_xa必須設置為1(默認為1且5.7.10后已經棄用)。
來具體分析下二階段提交各個階段crash的恢復情況:
  如果是在一階段(prepare階段)后crash,那么binlog未寫,事務回滾。
  如果在二階段第一步后crash,那么binlog已寫,重做事務。
由於二階段事務的提交是原子性的,這樣總能保證binlog與innodb的一致,即便出現了XA事務內的crash,也能合理的進行事務前滾或回滾。
對於主從復制的影響:
上面討論的二階段日志提交解決了mysql server與innodb層的日志一致性的問題,單實例的情況下最多因為sync_binlog和innodb_flush_log_at_trx_commit的設置問題導致事務丟失,但是對於主從復制事務丟失卻是很嚴重的問題-->主從不一致。
在主從復制的情況下如果innodb_flush_log_at_trx_commit不為1則有可能出現binlog已寫但是redo log未寫的情況,此時主庫崩潰后在事務前滾時會出現找不到redo的情況導致前滾失敗,而從庫已經應用binlog,導致主從不一致。
而sync_binlog不為1則可能出現主庫直接丟失事務的情況。
因此,為保證主從完全一致且事務不丟失,主庫的innodb_flush_log_at_trx_commit和sync_binlog都必須設置為1。
 
四、Binlog Group Commit的出現
以上提到單個事務的二階段提交過程(XA事務),可以保證 InnoDB redo和 binlog的一致性。

使用 prepare_commit_mutex 來保證事務提交的順序,只有當上一個事務 commit 后釋放鎖,下個事務才可以進行 prepare 操作,這樣並發事務之間的mutex爭用可能比較高。

由於內存數據寫入磁盤的開銷很大,如果頻繁 fsync() 把日志數據永久寫入磁盤,數據庫的性能將會急劇下降。高並發事務帶來的頻繁磁盤寫會導致事務提交等待帶來性能瓶頸,為此提供 sync_binlog 參數來設置多少個 binlog 日志產生的時候調用一次 fsync() 把二進制日志刷入磁盤來提高整體性能,但這可能導致主從不一致。

因此針對innodb事務出現了binlog的組提交方式,其基本原理就是將多個並發事務的binlog(3個以上)通過隊列機制一次性寫入磁盤,從而減小磁盤寫次數,也避免了prepare_commit_mutex 的爭用。

雖然組提交機制可以有效的提升高並發時的日志寫效率,但是官網也明確說明只有高並發時效果才比較顯著,如果數據庫沒什么並發反而效率還會降低。

改進方案:

Mysql5.6 引入了組提交,並將提交過程分成 Flush stage、Sync stage、Commit stage 三個階段。其實簡單的說就是加入隊列機制使得binlog寫入順序與事務執行順序一致,加入隊列的最大好處就是可以不獲取prepare_commit_mutex鎖也能實現不降低性能的日志順序寫。

Binlog組提交的基本思想是,引入隊列機制保證Innodb commit順序與binlog落盤順序一致,並將事務分組,組內的binlog刷盤動作交給一個事務進行,實現組提交目的。在MySQL數據庫上層進行提交時首先按順序將其放入一個隊列中,隊列中的第一個事務稱為leader,其他事務稱為follow,leader控制着follow的行為。

  • Flush Stage

1) 持有Lock_log mutex [leader持有,follower等待]。

2) 獲取隊列中的一組binlog(隊列中的所有事務)。

3) 將binlog buffer到I/O cache。

4) 通知dump線程dump binlog。

  • Sync Stage

1) 釋放Lock_log mutex,持有Lock_sync mutex[leader持有,follower等待]。

2) 將一組binlog 落盤(sync動作,最耗時,假設sync_binlog為1)。

  • Commit Stage

1) 釋放Lock_sync mutex,持有Lock_commit mutex[leader持有,follower等待]。

2) 遍歷隊列中的事務,逐一進行innodb commit。

3) 釋放Lock_commit mutex。

4) 喚醒隊列中等待的線程。

說明:由於有多個隊列,每個隊列各自有mutex保護,隊列之間是順序的,約定進入隊列的一個線程為leader,因此FLUSH階段的leader可能是SYNC階段的follower,但是follower永遠是follower。當有一組事務在進行commit階段時,其他新事物可以進行Flush階段,從而使group commit不斷生效。當然group commit的效果由隊列中事務的數量決定,若每次隊列中僅有一個事務,那么可能效果和之前差不多,甚至會更差。但當提交的事務越多時,group commit的效果越明顯,數據庫性能的提升也就越大。

與 binlog 組提交相關的參數主要包括了如下兩個:

  • binlog_max_flush_queue_time

單位為微秒,用於從 flush 隊列中取事務的超時時間,這主要是防止並發事務過高,導致某些事務的 RT 上升,詳細內容可以查看函數MYSQL_BIN_LOG::process_flush_stage_queue() 。

注意:該參數在 5.7 之后已經取消了。

  • binlog_order_commits

當設置為 0 時,事務可能以和 binlog 不同的順序提交,其性能會有稍微提升,但並不是特別明顯.


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM