亞寵展、全球?qū)櫸锂a(chǎn)業(yè)風(fēng)向標(biāo)——亞洲寵物展覽會(huì)深度解析
846
2025-03-31
在業(yè)務(wù)高峰期臨時(shí)提升性能的方法。
WAL機(jī)制保證只要redo log和binlog保證持久化到磁盤,就能確保Mysql異常重啟后,數(shù)據(jù)可以恢復(fù)。
binlog的寫入機(jī)制
事務(wù)執(zhí)行過程中:
先把日志寫到binlog cache
事務(wù)提交時(shí),再把binlog cache寫到binlog文件
一個(gè)事務(wù)的binlog不該被拆開,不論事務(wù)多大,也要確保一次性寫入。這就涉及binlog cache的保存問題。
系統(tǒng)給binlog cache分配了一片內(nèi)存,每個(gè)線程一個(gè),但是共用同一份binlog文件。參數(shù) binlog_cache_size控制單個(gè)線程內(nèi)binlog cache所占內(nèi)存的大小。若超過該參數(shù)值,就要暫存到磁盤。
事務(wù)提交時(shí),執(zhí)行器把binlog cache里的完整事務(wù)寫入binlog,并清空binlog cache。
binlog寫盤狀態(tài)
TODO
圖中的:
write
把日志寫入到文件系統(tǒng)的page cache,并沒有把數(shù)據(jù)持久化到磁盤,所以速度較快
fsync
將數(shù)據(jù)持久化到磁盤。一般認(rèn)為fsync才占磁盤的IOPS
write 和fsync的時(shí)機(jī),由參數(shù)sync_binlog控制:
sync_binlog=0,每次提交事務(wù)都只write,不fsync
sync_binlog=1,每次提交事務(wù)都會(huì)執(zhí)行fsync
sync_binlog=N(N>1),每次提交事務(wù)都write,但累積N個(gè)事務(wù)后才fsync
因此,在出現(xiàn)I/O瓶頸的場(chǎng)景,將sync_binlog設(shè)置成一個(gè)較大值,可提升性能。在實(shí)際的業(yè)務(wù)場(chǎng)景中,考慮到丟失日志量的可控性,一般不建議將這個(gè)參數(shù)設(shè)成0,推薦將其設(shè)置為100~1000中的某個(gè)數(shù)值。
但將sync_binlog設(shè)置為N,對(duì)應(yīng)的風(fēng)險(xiǎn)是:若主機(jī)發(fā)生異常重啟,會(huì)丟失最近N個(gè)事務(wù)的binlog日志。
redo log的寫入機(jī)制
接下來,我們?cè)僬f說redo log的寫入機(jī)制。
事務(wù)在執(zhí)行過程中,生成的redo log是要先寫到redo log buffer的。
那redo log buffer的內(nèi)容,是不是每次生成后都要直接持久化到磁盤呢?
不需要。
若事務(wù)執(zhí)行期間Mysql異常重啟,那這部分日志就丟了。由于事務(wù)也尚未提交,所以這時(shí)日志丟了也沒有損失。
那事務(wù)還沒提交時(shí),redo log buffer中的部分日志有沒有可能被持久化到磁盤呢?
會(huì)有。
這個(gè)問題,要從redo log可能存在的三種狀態(tài)說起。這三種狀態(tài),對(duì)應(yīng)的就是圖2 中的三個(gè)顏色塊。
MySQL redo log存儲(chǔ)狀態(tài)
TODO
三種狀態(tài):
存在redo log buffer
物理上是在MySQL進(jìn)程內(nèi)存
寫到磁盤(write),但還沒持久化(fsync)
物理上是在文件系統(tǒng)的page cache
持久化到磁盤,即hard disk
日志寫到redo log buffer很快,wirte到page cache也差不多,但持久化到磁盤就很慢了。
InnoDB提供innodb_flush_log_at_trx_commit參數(shù)控制redo log的寫入策略:
0,每次事務(wù)提交時(shí)都只是把redo log留在redo log buffer中
1,每次事務(wù)提交時(shí)都將redo log直接持久化到磁盤
2,每次事務(wù)提交時(shí)都只是把redo log寫到page cache
InnoDB的一個(gè)后臺(tái)線程,會(huì)每隔1s把redo log buffer中的日志,調(diào)用write寫到文件系統(tǒng)的page cache,然后調(diào)用fsync持久化到磁盤。
事務(wù)執(zhí)行中間過程的redo log也是直接寫在redo log buffer,這些redo log也會(huì)被后臺(tái)線程一起持久化到磁盤。即一個(gè)沒有提交的事務(wù)的redo log,也可能已經(jīng)持久化到磁盤。
除了后臺(tái)線程每s一次的輪詢操作,還有兩種場(chǎng)景會(huì)讓一個(gè)未提交的事務(wù)的redo log寫入磁盤:
redo log buffer占用的空間即將達(dá)到 innodb_log_buffer_size的一半,后臺(tái)線程會(huì)主動(dòng)寫盤
由于這個(gè)事務(wù)并未提交,所以這個(gè)寫盤動(dòng)作只是write,沒有調(diào)用fsync,即只留在文件系統(tǒng)的page cache。
并行的事務(wù)提交時(shí),順帶將該事務(wù)的redo log buffer持久化到磁盤
假設(shè)一個(gè)事務(wù)A執(zhí)行到一半,已經(jīng)寫了一些redo log到buffer,這時(shí)另外一個(gè)線程的事務(wù)B提交,若innodb_flush_log_at_trx_commit是1,則事務(wù)B要把redo log buffer里的日志全部持久化到磁盤。這時(shí),就會(huì)帶上事務(wù)A在redo log buffer里的日志一起持久化到磁盤。
兩階段提交的過程,時(shí)序上redo log先prepare,再寫binlog,最后再把redo log commit。
若把innodb_flush_log_at_trx_commit置1,則redo log在prepare階段就要持久化一次,因?yàn)橛幸粋€(gè)崩潰恢復(fù)邏輯是要依賴于prepare 的redo log,再加上binlog來恢復(fù)的。
每s一次的后臺(tái)輪詢刷盤,再加上崩潰恢復(fù),InnoDB就認(rèn)為redo log在commit時(shí)無需fsync,只write到文件系統(tǒng)的page cache就夠了。
通常我們說MySQL的“雙1”配置,指的就是sync_binlog、innodb_flush_log_at_trx_commit都是1。即一個(gè)事務(wù)完整提交前,需要等待兩次刷盤:
redo log(prepare 階段)
binlog
那這意味著我從MySQL看到TPS是2w,每秒就會(huì)寫四萬次磁盤。但我用工具測(cè)試,磁盤能力也就2w左右,怎么能實(shí)現(xiàn)2w TPS?
得用組提交(group commit)來解釋了。
日志邏輯序列號(hào)(log sequence number,LSN)
LSN單調(diào)遞增,對(duì)應(yīng)redo log的寫入點(diǎn)。比如寫入length長(zhǎng)度的redo log, 則LSN+length。
LSN也會(huì)寫到InnoDB的數(shù)據(jù),以確保數(shù)據(jù)頁(yè)不會(huì)被多次執(zhí)行重復(fù)的redo log。
如圖3所示,是三個(gè)并發(fā)事務(wù)(trx1, trx2, trx3)在prepare 階段,都寫完redo log buffer,持久化到磁盤的過程,對(duì)應(yīng)的LSN分別是50、120 和160。
redo log 組提交
TODO
trx1第一個(gè)到達(dá),被選為這組的leader
等trx1要開始寫盤,組里已經(jīng)有了三個(gè)事務(wù),LSN也變成了160
trx1去寫盤時(shí),帶的就是LSN=160。所以,等trx1返回時(shí),所有LSN≤160的redo log,都已被持久化到磁盤
這時(shí),trx2和trx3就可直接返回
所以,一次組提交里,組員越多,節(jié)約磁盤IOPS效果越好。但若只有單線程壓測(cè),則只能老老實(shí)實(shí)地一個(gè)事務(wù)對(duì)應(yīng)一次持久化操作。
在并發(fā)更新場(chǎng)景下,第一個(gè)事務(wù)寫完redo log buffer后,接下來這個(gè)fsync越晚調(diào)用,組員可能越多,節(jié)約IOPS效果越好。
為了讓一次fsync帶的組員更多,MySQL采取優(yōu)化:拖時(shí)間。
兩階段提交
寫binlog實(shí)際上分成兩步:
先把binlog從binlog cache中寫到磁盤上的binlog文件
調(diào)用fsync持久化
MySQL為了讓組提交效果更好,把redo log做fsync的時(shí)間拖到了step1后面:
兩階段提交細(xì)化
這樣的話,binlog也可以組提交。上圖的step4時(shí),若有多個(gè)事務(wù)的binlog已經(jīng)寫完,也是一起持久化的,這樣也能減少IOPS。
一般step3執(zhí)行很快,所以binlog的write、fsync間隔時(shí)間很短,導(dǎo)致能集合到一起持久化的binlog較少,因此binlog的組提交的效果通常不如redo log的效果。
若想提升binlog組提交效果,可設(shè)置:
binlog_group_commit_sync_delay參數(shù)
延遲多少微秒后才調(diào)用fsync
binlog_group_commit_sync_no_delay_count參數(shù)
累積多少次以后才調(diào)用fsync
這兩個(gè)條件是或的關(guān)系,即只要有一個(gè)滿足條件就會(huì)調(diào)用fsync。
這樣的話,binlog_group_commit_sync_delay = 0 時(shí),binlog_group_commit_sync_no_delay_count就無效了。
WAL是減少磁盤寫,可每次提交事務(wù)都要寫redo log和binlog,這磁盤的讀寫次數(shù)也沒變少呀?s所以現(xiàn)在就能理解了,WAL主要得益于:
redo log 和 binlog都是順序?qū)?,磁盤的順序?qū)懕入S機(jī)寫速度要快
組提交機(jī)制,可大幅度降低磁盤IOPS
所以,若MySQL出現(xiàn)IO性能瓶頸,可通過如下方法優(yōu)化:
設(shè)置 binlog_group_commit_sync_delay 、binlog_group_commit_sync_no_delay_count,減少binlog寫盤次數(shù)
該方案是基于“額外的故意等待”來實(shí)現(xiàn)的,因此可能會(huì)增加語(yǔ)句的響應(yīng)時(shí)間,但不會(huì)丟數(shù)據(jù)
將sync_binlog 設(shè)為大于1的值(推薦100~1000)
風(fēng)險(xiǎn)是,主機(jī)掉電時(shí)會(huì)丟binlog日志。
將innodb_flush_log_at_trx_commit設(shè)為2
風(fēng)險(xiǎn)是,主機(jī)掉電的時(shí)候會(huì)丟數(shù)據(jù)。
不推薦把innodb_flush_log_at_trx_commit 設(shè)成0。因?yàn)榇藭r(shí)表示redo log只保存在內(nèi)存,這樣MySQL本身異常重啟也會(huì)丟數(shù)據(jù),風(fēng)險(xiǎn)太大。而redo log寫到文件系統(tǒng)的page cache的速度是很快的,所以將該參數(shù)設(shè)成2跟設(shè)成0性能差不多,但這樣做MySQL異常重啟時(shí)就不會(huì)丟數(shù)據(jù)了。
小結(jié)
MySQL是“怎么保證redo log和binlog是完整的”。
crash-safe
執(zhí)行一個(gè)update后,再執(zhí)行hexdump直接查看ibd文件內(nèi)容,為什么沒有看到數(shù)據(jù)有改變?
可能因?yàn)閃AL。update語(yǔ)句執(zhí)行完后,InnoDB只保證寫完了redo log、內(nèi)存,可能還沒來得及將數(shù)據(jù)寫磁盤。
為什么binlog cache是每個(gè)線程自己維護(hù)的,而redo log buffer是全局共用?
binlog不能“被打斷”。一個(gè)事務(wù)的binlog必須連續(xù)寫,因此要整個(gè)事務(wù)完成后,再一起寫到文件。
而redo log沒有這個(gè)要求,中間有生成的日志可以寫到redo log buffer。redo log buffer中的內(nèi)容還能“搭便車”,其他事務(wù)提交的時(shí)候可以被一起寫到磁盤。
事務(wù)執(zhí)行期間,還沒到提交階段,若發(fā)生crash,redo log肯定丟了,這會(huì)不會(huì)導(dǎo)致主備不一致呢?
不會(huì)。因?yàn)榇藭r(shí)binlog還在binlog cache,沒發(fā)給備庫(kù)。crash之后,redo log和binlog都沒有了,從業(yè)務(wù)角度看這個(gè)事務(wù)也沒有提交,所以數(shù)據(jù)是一致的。
若binlog寫完盤以后發(fā)生crash,這時(shí)還沒給客戶端答復(fù)就重啟了。等客戶端再重連進(jìn)來,發(fā)現(xiàn)事務(wù)已經(jīng)提交成功了,這是不是bug?
不是。設(shè)想一下更極端場(chǎng)景,整個(gè)事務(wù)都提交成功,redo log commit完成了,備庫(kù)也收到binlog并執(zhí)行了。但主庫(kù)和客戶端網(wǎng)絡(luò)斷了,導(dǎo)致事務(wù)成功的包返回不回去,這時(shí)客戶端也會(huì)收到“網(wǎng)絡(luò)斷開”的異常。這種也只能算是事務(wù)成功的,不能認(rèn)為是bug。
實(shí)際上DB的crash-safe保證的是:
如果客戶端收到事務(wù)成功的消息,事務(wù)就一定持久化了
如果客戶端收到事務(wù)失?。ū热缰麈I沖突、回滾等)的消息,事務(wù)就一定失敗了
如果客戶端收到“執(zhí)行異?!钡南?,應(yīng)用需要重連后通過查詢當(dāng)前狀態(tài)來繼續(xù)后續(xù)的邏輯。此時(shí)DB只需要保證內(nèi)部(數(shù)據(jù)和日志之間,主庫(kù)和備庫(kù)之間)一致即可。
MySQL
版權(quán)聲明:本文內(nèi)容由網(wǎng)絡(luò)用戶投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔(dān)相應(yīng)法律責(zé)任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實(shí)的內(nèi)容,請(qǐng)聯(lián)系我們jiasou666@gmail.com 處理,核實(shí)后本網(wǎng)站將在24小時(shí)內(nèi)刪除侵權(quán)內(nèi)容。
版權(quán)聲明:本文內(nèi)容由網(wǎng)絡(luò)用戶投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔(dān)相應(yīng)法律責(zé)任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實(shí)的內(nèi)容,請(qǐng)聯(lián)系我們jiasou666@gmail.com 處理,核實(shí)后本網(wǎng)站將在24小時(shí)內(nèi)刪除侵權(quán)內(nèi)容。