科多大數(shù)據(jù)小課堂之MySQL Group Commit的優(yōu)化
背景
關(guān)于Group Commit網(wǎng)上的資料其實(shí)已經(jīng)足夠多了卵凑,我這里只簡(jiǎn)單的介紹一下旁趟。
眾所周知贾节,在MySQL5.6之前的版本袖肥,由于引入了Binlog/InnoDB的XA火欧,Binlog的寫(xiě)入和InnoDB commit完全串行化執(zhí)行棋电,大概的執(zhí)行序列如下:
Mysql代碼
1.InnoDB prepare (持有prepare_commit_mutex)茎截;
2.write/sync Binlog;
3.InnoDB commit (寫(xiě)入COMMIT標(biāo)記后釋放prepare_commit_mutex)赶盔。
當(dāng)sync_binlog=1時(shí)企锌,很明顯上述的第二步會(huì)成為瓶頸,而且還是持有全局大鎖于未,這也是為什么性能會(huì)急劇下降撕攒。
很快Mariadb就提出了一個(gè)Binlog Group Commit方案,即在準(zhǔn)備寫(xiě)入Binlog時(shí)沉眶,維持一個(gè)隊(duì)列打却,最早進(jìn)入隊(duì)列的是leader,后來(lái)的是follower谎倔,leader為搜集到的隊(duì)列中的線程依次寫(xiě)B(tài)inlog文件, 并commit事務(wù)柳击。Percona 的Group Commit實(shí)現(xiàn)也是Port自Mariadb。不過(guò)仍在使用Percona Server5.5的朋友需要注意片习,該Group Commit實(shí)現(xiàn)可能破壞掉Semisync的行為捌肴,感興趣的點(diǎn)擊bug#1254571
Oracle MySQL 在5.6版本開(kāi)始也支持Binlog Group Commit,使用了和Mariadb類似的思路藕咏,但將Group Commit的過(guò)程拆分成了三個(gè)階段:flush stage 將各個(gè)線程的binlog從cache寫(xiě)到文件中; sync stage 對(duì)binlog做fsync操作(如果需要的話)状知;commit stage 為各個(gè)線程做引擎層的事務(wù)commit。每個(gè)stage同時(shí)只有一個(gè)線程在操作孽查。
Tips:當(dāng)引入Group Commit后饥悴,sync_binlog的含義就變了,假定設(shè)為1000盲再,表示的不是1000個(gè)事務(wù)后做一次fsync西设,而是1000個(gè)事務(wù)組。
Oracle MySQL的實(shí)現(xiàn)的優(yōu)勢(shì)在于三個(gè)階段可以并發(fā)執(zhí)行答朋,從而提升效率贷揽。
XA Recover
在Binlog打開(kāi)的情況下,MySQL默認(rèn)使用MySQL_BIN_LOG來(lái)做XA協(xié)調(diào)者梦碗,大致流程為:
引用
1.掃描最后一個(gè)Binlog文件禽绪,提取其中的xid;
2.InnoDB維持了狀態(tài)為Prepare的事務(wù)鏈表洪规,將這些事務(wù)的xid和Binlog中記錄的xid做比較印屁,如果在Binlog中存在,則提交斩例,否則回滾事務(wù)库车。
通過(guò)這種方式,可以讓InnoDB和Binlog中的事務(wù)狀態(tài)保持一致樱拴。顯然只要事務(wù)在InnoDB層完成了Prepare柠衍,并且寫(xiě)入了Binlog,就可以從崩潰中恢復(fù)事務(wù)晶乔,這意味著我們無(wú)需在InnoDB commit時(shí)顯式的write/fsync redo log珍坊。
Tips:MySQL為何只需要掃描最后一個(gè)Binlog文件呢 ? 原因是每次在rotate到新的Binlog文件時(shí)正罢,總是保證沒(méi)有正在提交的事務(wù)阵漏,然后fsync一次InnoDB的redo log。這樣就可以保證老的Binlog文件中的事務(wù)在InnoDB總是提交的翻具。
問(wèn)題
其實(shí)問(wèn)題很簡(jiǎn)單:每個(gè)事務(wù)都要保證其Prepare的事務(wù)被write/fsync到redo log文件履怯。盡管某個(gè)事務(wù)可能會(huì)幫助其他事務(wù)完成redo 寫(xiě)入,但這種行為是隨機(jī)的裆泳,并且依然會(huì)產(chǎn)生明顯的log_sys->mutex開(kāi)銷叹洲。
優(yōu)化
從XA恢復(fù)的邏輯我們可以知道,只要保證InnoDB Prepare的redo日志在寫(xiě)B(tài)inlog前完成write/sync即可工禾。因此我們對(duì)Group Commit的第一個(gè)stage的邏輯做了些許修改运提,大概描述如下:
引用
Step1. InnoDB Prepare,記錄當(dāng)前的LSN到thd中闻葵;
Step2. 進(jìn)入Group Commit的flush stage民泵;Leader搜集隊(duì)列,同時(shí)算出隊(duì)列中最大的LSN槽畔。
Step3. 將InnoDB的redo log write/fsync到指定的LSN
Step4. 寫(xiě)B(tài)inlog并進(jìn)行隨后的工作(sync Binlog, InnoDB commit , etc)
通過(guò)延遲寫(xiě)redo log的方式栈妆,顯式的為redo log做了一次組寫(xiě)入,并減少了log_sys->mutex的競(jìng)爭(zhēng)厢钧。
目前官方MySQL已經(jīng)根據(jù)我們r(jià)eport的bug#73202鎖提供的思路鳞尔,對(duì)5.7.6的代碼進(jìn)行了優(yōu)化,對(duì)應(yīng)的Release Note如下:
Java代碼
1.When using InnoDB with binary logging enabled, concurrent transactions written in the InnoDB redo log are now grouped together before synchronizing to disk when innodb_flush_log_at_trx_commit is set to 1, which reduces the amount of synchronization operations. This can lead to improved performance.
性能數(shù)據(jù)
簡(jiǎn)單測(cè)試了下坏快,使用sysbench, update_non_index.lua, 100張表铅檩,每張10w行記錄,innodb_flush_log_at_trx_commit=2, sync_binlog=1000莽鸿,關(guān)閉Gtid
引用
并發(fā)線程 原生 修改后
32 25600 27000
64 30000 35000
128 33000 39000
256 29800 38000