溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

MySQL binlog和redo的組提交

發(fā)布時(shí)間:2020-08-09 11:11:17 來源:ITPUB博客 閱讀:487 作者:賀子_DBA時(shí)代 欄目:MySQL數(shù)據(jù)庫(kù)
整體概述:組提交(group commit)是MYSQL處理日志的一種優(yōu)化方式,主要為了解決寫日志時(shí)頻繁刷磁盤的問題。組提交伴隨著MYSQL的發(fā)展不斷優(yōu)化,從最初只支持redo log 組提交,到目前5.6官方版本同時(shí)支持redo log 和binlog組提交。組提交的實(shí)現(xiàn)大大提高了mysql的事務(wù)處理性能
mysql5.6官方版本之前:
5.6之前只支持redo組提交,當(dāng)開啟了binlog,為了保證redo和binlog的一致性,使用了兩階段提交(先刷新redo,再刷新binlog,并且以binlog刷新成功與否來判斷事務(wù)是否成功),為了保證binlog和commit的順序一致,binlog使用了串行化的刷新,導(dǎo)致binlog無法組提交, 5.6優(yōu)化了這個(gè)步驟,引入了隊(duì)列,保證了binlog有順序,進(jìn)而binlog也可以組提交;
接下來重點(diǎn)說說一:redo 組提交,二:兩階段提交。三:5.6 binlog組提交;
一:redo 組提交
WAL(Write-Ahead-Logging)是實(shí)現(xiàn)事務(wù)持久性的一個(gè)常用技術(shù),基本原理是在提交事務(wù)時(shí),為了避免磁盤頁面的隨機(jī)寫,只需要保證事務(wù)的redo log寫入磁盤即可,這樣可以通過redo log的順序?qū)懘骓撁娴碾S機(jī)寫,并且可以保證事務(wù)的持久性,提高了數(shù)據(jù)庫(kù)系統(tǒng)的性能。雖然WAL使用順序?qū)懱娲穗S機(jī)寫,但是,每次事務(wù)提交,仍然需要有一次日志刷盤動(dòng)作,受限于磁盤IO,這個(gè)操作仍然是事務(wù)并發(fā)的瓶頸。
redo組提交思想是,將多個(gè)事務(wù)redo log的刷盤動(dòng)作合并,減少磁盤順序?qū)憽nnodb的日志系統(tǒng)里面,每條redo log都有一個(gè)LSN(Log Sequence Number),LSN是單調(diào)遞增的。每個(gè)事務(wù)執(zhí)行更新操作都會(huì)包含一條或多條redo log,各個(gè)事務(wù)將日志拷貝到log_sys_buffer時(shí)(log_sys_buffer 通過log_mutex保護(hù)),都會(huì)獲取當(dāng)前最大的LSN,因此可以保證不同事務(wù)的LSN不會(huì)重復(fù)。那么假設(shè)三個(gè)事務(wù)Trx1,Trx2和Trx3的日志的最大LSN分別為L(zhǎng)SN1,LSN2,LSN3(LSN1<lsn2<lsn3),它們同時(shí)進(jìn)行提交,那么如果trx3日志先獲取到log_mutex進(jìn)行落盤,它就可以順便把[lsn1---lsn3]這段日志也刷了,這樣trx1和trx2就不用再次請(qǐng)求磁盤io。組提交的基本流程如下:</lsn2<lsn3),它們同時(shí)進(jìn)行提交,那么如果trx3日志先獲取到log_mutex進(jìn)行落盤,它就可以順便把[lsn1---lsn3]這段日志也刷了,這樣trx1和trx2就不用再次請(qǐng)求磁盤io。組
1)獲取 log_mutex
2)若flushed_to_disk_lsn>=lsn,表示日志已經(jīng)被刷盤,跳轉(zhuǎn)5
3)若 current_flush_lsn>=lsn,表示日志正在刷盤中,跳轉(zhuǎn)5后進(jìn)入等待狀態(tài)
4)將小于LSN的日志刷盤(flush and sync)
5)退出log_mutex
備注:lsn表示事務(wù)的lsn,flushed_to_disk_lsn和current_flush_lsn分別表示已刷盤的LSN和正在刷盤的LSN。
redo log 組提交優(yōu)化
我們知道,在開啟binlog的情況下,prepare階段,會(huì)對(duì)redo log進(jìn)行一次刷盤操作(innodb_flush_log_at_trx_commit=1),確保對(duì)data頁和undo 頁的更新已經(jīng)刷新到磁盤;commit階段,會(huì)進(jìn)行刷binlog操作(sync_binlog=1),并且會(huì)對(duì)事務(wù)的undo log從prepare狀態(tài)設(shè)置為提交狀態(tài)(可清理狀態(tài))。通過兩階段提交方式(innodb_support_xa=1),可以保證事務(wù)的binlog和redo log順序一致。二階段提交過程中,mysql_binlog作為協(xié)調(diào)者,各個(gè)存儲(chǔ)引擎和mysql_binlog作為參與者。故障恢復(fù)時(shí),掃描最后一個(gè)binlog文件(在flush階段,判斷binlog是否超過閥值,進(jìn)行rotate binlog文件,rotate的binlog文件中對(duì)應(yīng)的事務(wù)一定是已經(jīng)提交的,處于prepared的事務(wù)的binlog還沒有刷進(jìn)來,因?yàn)檫€沒進(jìn)入ordered_commit函數(shù)),提取其中的xid;重做檢查點(diǎn)以后的redo日志,讀取事務(wù)的undo段信息,搜集處于prepare階段的事務(wù)鏈表,將事務(wù)的xid與binlog中的xid對(duì)比,若存在,則提交,否則就回滾。
通過上述的描述可知,每個(gè)事務(wù)提交時(shí),都會(huì)觸發(fā)一次redo flush動(dòng)作,由于磁盤讀寫比較慢,因此很影響系統(tǒng)的吞吐量。淘寶童鞋做了一個(gè)優(yōu)化,將prepare階段的刷redo動(dòng)作移到了commit(flush-sync-commit)的flush階段之前,保證刷binlog之前,一定會(huì)刷redo。這樣就不會(huì)違背原有的故障恢復(fù)邏輯。移到commit階段的好處是,可以不用每個(gè)事務(wù)都刷盤,而是leader線程幫助刷一批redo。如何實(shí)現(xiàn),很簡(jiǎn)單,因?yàn)閘og_sys->lsn始終保持了當(dāng)前最大的lsn,只要我們刷redo刷到當(dāng)前的log_sys->lsn,就一定能保證,將要刷binlog的事務(wù)redo日志一定已經(jīng)落盤。通過延遲寫redo方式,實(shí)現(xiàn)了redo log組提交的目的,而且減少了log_sys->mutex的競(jìng)爭(zhēng)。目前這種策略已經(jīng)被官方mysql5.7.6引入。
二:兩階段提交
在單機(jī)情況下,redo log組提交很好地解決了日志落盤問題,那么開啟binlog后,binlog能否和redo log一樣也開啟組提交?首先開啟binlog后,我們要解決的一個(gè)問題是,如何保證binlog和redo log的一致性。因?yàn)閎inlog是Master-Slave的橋梁,如果順序不一致,意味著Master-Slave可能不一致。MYSQL通過兩階段提交很好地解決了這一問題。
Prepare階段:innodb刷redo log,并將回滾段設(shè)置為Prepared狀態(tài),binlog不作任何操作;
commit階段:innodb釋放鎖,釋放回滾段,設(shè)置提交狀態(tài),binlog刷binlog日志。
出現(xiàn)異常,需要故障恢復(fù)時(shí),若發(fā)現(xiàn)事務(wù)處于Prepare階段,并且binlog存在則提交,否則回滾。通過兩階段提交,保證了redo log和binlog在任何情況下的一致性。
伴隨著這個(gè)問題,我重點(diǎn)說下,MySQL innodb 引擎事務(wù)commit的過程:
MySQL為了保證master和slave的數(shù)據(jù)一致性,就必須保證binlog和InnoDB redo日志的一致性(因?yàn)閭鋷?kù)通過二進(jìn)制日志重放主庫(kù)提交的事務(wù),而主庫(kù)binlog寫入在commit之前,如果寫完binlog主庫(kù)crash,再次啟動(dòng)時(shí)會(huì)回滾事務(wù)。但此時(shí)從庫(kù)已經(jīng)執(zhí)行,則會(huì)造成主備數(shù)據(jù)不一致)。所以在開啟Binlog后,如何保證binlog和InnoDB redo日志的一致性呢?為此,MySQL引入二階段提交(two phase commit or 2pc),MySQL內(nèi)部會(huì)自動(dòng)將普通事務(wù)當(dāng)做一個(gè)XA事務(wù)(內(nèi)部分布式事物)來處理:
MySQL通過兩階段提交(內(nèi)部XA的兩階段提交)很好地解決了這一問題,兩階段提交關(guān)鍵在于保證redo刷盤之后才能寫binloglog 文件;MySQL5.6以前,為了保證數(shù)據(jù)庫(kù)上層二進(jìn)制日志的寫入順序和InnoDB層的事務(wù)提交順序一致,MySQL數(shù)據(jù)庫(kù)內(nèi)部使用了prepare_commit_mutex鎖。但是持有這把鎖之后,會(huì)導(dǎo)致組提交失??;
      回到上節(jié)的問題,開啟binlog后,如何在保證redo log-binlog一致的基礎(chǔ)上,實(shí)現(xiàn)組提交。因?yàn)檫@個(gè)問題,5.6以前,mysql在開啟binlog的情況下,無法實(shí)現(xiàn)組提交,通過一個(gè)臭名昭著的prepare_commit_mutex,將binlog刷盤串行化,串行化的目的也僅僅是為了保證redo log-Binlog一致,但這種實(shí)現(xiàn)方式犧牲了性能。這個(gè)情況顯然是不能容忍的,因此各個(gè)mysql分支,mariadb,facebook,perconal等相繼出了補(bǔ)丁改進(jìn)這一問題,mysql官方版本5.6也終于解決了這一問題。由于各個(gè)分支版本解決方法類似,我主要通過分析5.6的實(shí)現(xiàn)來說明實(shí)現(xiàn)方法。
     binlog組提交的基本思想是,引入隊(duì)列機(jī)制保證innodb commit順序與binlog落盤順序一致,并將事務(wù)分組,組內(nèi)的binlog刷盤動(dòng)作交給一個(gè)事務(wù)進(jìn)行,實(shí)現(xiàn)組提交目的。binlog提交將提交分為了3個(gè)階段,F(xiàn)LUSH階段,SYNC階段和COMMIT階段。每個(gè)階段都有一個(gè)隊(duì)列,每個(gè)隊(duì)列有一個(gè)mutex保護(hù),約定進(jìn)入隊(duì)列第一個(gè)線程為leader,其他線程為follower,所有事情交由leader去做,leader做完所有動(dòng)作后,通知follower刷盤結(jié)束。
MySQL5.6之前的如下兩個(gè)階段:
第一階段(準(zhǔn)備階段):InnoDB prepare,持有prepare_commit_mutex,并且write/sync redo log; 將回滾段設(shè)置為Prepared狀態(tài),binlog不作任何操作;
第二個(gè)階段(提交階段):Commit Phase 包含兩步
1.write/sync Binlog(這里為了保證和redo的順序一樣只能串行化的刷新);
2.InnoDB commit (寫入COMMIT標(biāo)記后釋放prepare_commit_mutex,釋放回滾段,設(shè)置提交狀態(tài));
以 binlog 的寫入與否作為事務(wù)提交成功與否的標(biāo)志,innodb commit標(biāo)志并不是事務(wù)提交成功與否的標(biāo)志。因?yàn)榇藭r(shí)的事務(wù)崩潰恢復(fù)過程如下:
1.崩潰恢復(fù)時(shí),掃描最后一個(gè)Binlog文件,提取其中的xid;
2.InnoDB維持了狀態(tài)為Prepare的事務(wù)鏈表,將這些事務(wù)的xid和Binlog中記錄的xid做比較,如果在Binlog中存在,則提交,否則回滾事務(wù)。
通過這種方式,可以讓InnoDB redo 和Binlog中的事務(wù)狀態(tài)保持一致。
在寫入innodb commit標(biāo)志時(shí)崩潰,則恢復(fù)時(shí),會(huì)借助redo log 重新對(duì)commit標(biāo)志進(jìn)行寫入;
在prepare階段崩潰,則binlog中肯定沒有這些xid,則借助undo 回滾;
在write/sync binlog階段崩潰,也會(huì)借助undo回滾。
MySQL5.6之前在開啟Binary log時(shí)使用prepare_commit_mutex和sync_log保證二進(jìn)制日志和存儲(chǔ)引擎順序保持一致,prepare_commit_mutex的鎖機(jī)制造成高并發(fā)提交事務(wù)的時(shí)候性能非常差而且二進(jìn)制日志也無法group commit;
MySQL5.6以及之后版本中的實(shí)現(xiàn)方式,重點(diǎn)解決了binlog組提交的問題:
上面的事務(wù)的兩階段提交過程是5.6之前版本中的實(shí)現(xiàn),有嚴(yán)重的缺陷。當(dāng)sync_binlog=1時(shí),很明顯上述的第二階段中的 write/sync binlog會(huì)成為瓶頸,而且還是持有全局大鎖(prepare_commit_mutex: prepare 和 commit共用一把鎖),這會(huì)導(dǎo)致性能急劇下降。
解決辦法就是MySQL5.6中的 binlog組提交。
binlog組提交的基本思想是,引入隊(duì)列機(jī)制保證innodb commit順序與binlog落盤順序一致,并將事務(wù)分組,組內(nèi)的binlog刷盤動(dòng)作交給一個(gè)事務(wù)進(jìn)行,實(shí)現(xiàn)組提交目的。
binlog提交將提交階段分為了3個(gè)階段,F(xiàn)LUSH階段,SYNC階段和COMMIT階段。每個(gè)階段都有一個(gè)隊(duì)列,隊(duì)列中的第一個(gè)事務(wù)稱為leader,其他事務(wù)稱為follower,leader控制著follower的行為。
MySQL5.6之后的MySQL innodb 引擎事務(wù)commit的過程:
第一階段(準(zhǔn)備階段):InnoDB prepare,持有prepare_commit_mutex,并且write/sync redo log; 將回滾段設(shè)置為Prepared狀態(tài),完成后就釋放prepare_commit_mutex,binlog不作任何操作;
第二個(gè)階段(提交階段):InnoDB 提交階段發(fā)生變化了,將Commit階段拆分成了三步,每個(gè)階段的任務(wù)分配給一個(gè)專門的線程:
FLUSH 階段
1) 持有Lock_log mutex [leader持有,follower等待]
2) 獲取隊(duì)列中的一組binlog(隊(duì)列中的所有事務(wù))
3) 將binlog buffer到I/O cache
4) 通知dump線程dump binlog
SYNC階段
這個(gè)階段和參數(shù)sync_binlog有關(guān)系,
1) 釋放Lock_log mutex,持有Lock_sync mutex[leader持有,follower等待]
2) 將一組binlog 落盤(sync動(dòng)作,最耗時(shí),假設(shè)sync_binlog為1)。
COMMIT階段(這里不用寫redo log,在prepare階段已寫)
1) 釋放Lock_sync mutex,持有Lock_commit mutex[leader持有,follower等待]
2) 遍歷隊(duì)列中的事務(wù),逐一進(jìn)行innodb commit
3) 釋放Lock_commit mutex
4) 喚醒隊(duì)列中等待的線程
總結(jié)起來就是 :
FLUSH 階段-----將binlog從binlog buffer到I/O cache刷新;
SYNC階段-------將binlog從I/O cache到底層磁盤;
COMMIT階段----innodb commit ,清除undo信息;
每個(gè)stage都有自己的隊(duì)列。每個(gè)隊(duì)列各自有mutex保護(hù),隊(duì)列之間是順序的。只有flush完成后,才能進(jìn)入到sync階段的隊(duì)列中;sync完成后,才能進(jìn)入到commit階段的隊(duì)列中。但是,這三個(gè)階段的作業(yè)是可以同時(shí)并發(fā)執(zhí)行的,即當(dāng)一組事務(wù)在進(jìn)行commit階段時(shí),其他新事務(wù)可以進(jìn)行flush階段,實(shí)現(xiàn)了真正意義上的group commit。
向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI