MySQL實戰(zhàn)45講Day22----如何在臨時提升性能的同時保持?jǐn)?shù)據(jù)的可靠性

一、WAL機制:

1、意義:

??只要redo log和binlog保證持久化到磁盤,就能確保MySQL異常重啟后,數(shù)據(jù)可以恢復(fù)。

2、WAL機制主要得益于兩個方面:

?①、redo log 和 binlog都是順序?qū)?,磁盤的順序?qū)懕入S機寫速度要快;
?②、組提交機制,可以大幅度降低磁盤的IOPS消耗。

二、binlog的寫入機制:

1、寫入過程:

??事務(wù)執(zhí)行過程中,先把日志寫到binlog cache,事務(wù)提交的時候,再把binlog cache寫到binlog文件中。一個事務(wù)的binlog是不能被拆開的,因此不論這個事務(wù)多大,也要確保一次性寫入。這就涉及到了binlog cache的保存問題。系統(tǒng)給binlog cache分配了一片內(nèi)存,每個線程一個,參數(shù) binlog_cache_size用于控制單個線程內(nèi)binlog cache所占內(nèi)存的大小。如果超過了這個參數(shù)規(guī)定的大小,就要暫存到磁盤。事務(wù)提交的時候,執(zhí)行器把binlog cache里的完整事務(wù)寫入到binlog中,并清空binlog cache。

binlog寫盤狀態(tài)

由圖可以看出:
?①、每個線程有自己binlog cache,但是共用同一份binlog文件。
?②、圖中的write,指的就是指把日志寫入到文件系統(tǒng)的page cache,并沒有把數(shù)據(jù)持久化到磁盤,所以速度比較快。
?③、圖中的fsync,才是將數(shù)據(jù)持久化到磁盤的操作。一般情況下,我們認(rèn)為fsync才占磁盤的IOPS。

2、write 和fsync的時機:

是由參數(shù)sync_binlog控制的:
?sync_binlog=0的時候,表示每次提交事務(wù)都只write,不fsync;
?sync_binlog=1的時候,表示每次提交事務(wù)都會執(zhí)行fsync;
?sync_binlog=N(N>1)的時候,表示每次提交事務(wù)都write,但累積N個事務(wù)后才fsync。
因此,在出現(xiàn)IO瓶頸的場景里,將sync_binlog設(shè)置成一個比較大的值,可以提升性能。在實際的業(yè)務(wù)場景中,考慮到丟失日志量的可控性,一般不建議將這個參數(shù)設(shè)成0,比較常見的是將其設(shè)置為100~1000中的某個數(shù)值。但是,將sync_binlog設(shè)置為N,對應(yīng)的風(fēng)險是:如果主機發(fā)生異常重啟,會丟失最近N個事務(wù)的binlog日志。

三、redo log的寫入機制:

1、寫入過程:

??事務(wù)在執(zhí)行過程中,生成的redo log先寫到redo log buffer。

2、redo log buffer里面的內(nèi)容,是不是每次生成后都要直接持久化到磁盤?

??不是。如果事務(wù)執(zhí)行期間MySQL發(fā)生異常重啟,那這部分日志就丟了。由于事務(wù)并沒有提交,所以這時日志丟了也不會有損失。

3、事務(wù)還沒提交的時候,redo log buffer中的部分日志有沒有可能被持久化到磁盤呢?

redo log存儲狀態(tài)

??redo log可能存在的三種狀態(tài):
?①、存在redo log buffer中,物理上是在MySQL進(jìn)程內(nèi)存中,就是圖中的紅色部分;
?②、寫到磁盤(write),但是沒有持久化(fsync),物理上是在文件系統(tǒng)的page cache里面,也就是圖中的黃色部分;
?③、持久化到磁盤,對應(yīng)的是hard disk,也就是圖中的綠色部分。
??日志寫到redo log buffer是很快的,wirte到page cache也差不多,但是持久化到磁盤的速度就慢多了。為了控制redo log的寫入策略,InnoDB提供了innodb_flush_log_at_trx_commit參數(shù),它有三種可能取值:
?設(shè)置為0的時候,表示每次事務(wù)提交時都只是把redo log留在redo log buffer中;
?設(shè)置為1的時候,表示每次事務(wù)提交時都將redo log直接持久化到磁盤;
?設(shè)置為2的時候,表示每次事務(wù)提交時都只是把redo log寫到page cache。

InnoDB有一個后臺線程,每隔1秒,就會把redo log buffer中的日志,調(diào)用write寫到文件系統(tǒng)的page cache,然后調(diào)用fsync持久化到磁盤。

注意:事務(wù)執(zhí)行中間過程的redo log也是直接寫在redo log buffer中的,這些redo log也會被后臺線程一起持久化到磁盤。也就是說,一個沒有提交的事務(wù)的redo log,也是可能已經(jīng)持久化到磁盤的。

4、讓一個沒有提交的事務(wù)的redo log寫入到磁盤中的三種場景:

?①、后臺線程每秒一次的輪詢操作。
?②、redo log buffer占用的空間即將達(dá)到 innodb_log_buffer_size一半的時候,后臺線程會主動寫盤。注意,由于這個事務(wù)并沒有提交,所以這個寫盤動作只是write,而沒有調(diào)用fsync,也就是只留在了文件系統(tǒng)的page cache。
?③、并行的事務(wù)提交的時候,順帶將這個事務(wù)的redo log buffer持久化到磁盤。假設(shè)一個事務(wù)A執(zhí)行到一半,已經(jīng)寫了一些redo log到buffer中,這時候有另外一個線程的事務(wù)B提交,如果innodb_flush_log_at_trx_commit設(shè)置的是1,那么按照這個參數(shù)的邏輯,事務(wù)B要把redo log buffer里的日志全部持久化到磁盤。這時候,就會帶上事務(wù)A在redo log buffer里的日志一起持久化到磁盤。

四、MySQL的“雙1”配置:

1、概念:

??指的是sync_binlog和innodb_flush_log_at_trx_commit都設(shè)置成 1。也就是說,一個事務(wù)完整提交前,需要等待兩次刷盤,一次是redo log(prepare 階段),一次是binlog。

2、組提交(group commit)機制:

<1>、日志邏輯序列號(log sequence number,LSN):

??LSN是單調(diào)遞增的,用來對應(yīng)redo log的一個個寫入點。每次寫入長度為length的redo log, LSN的值就會加上length。LSN也會寫到InnoDB的數(shù)據(jù)頁中,來確保數(shù)據(jù)頁不會被多次執(zhí)行重復(fù)的redo log。

<2>、組提交的過程:

??如圖所示,是三個并發(fā)事務(wù)(trx1, trx2, trx3)在prepare 階段,都寫完redo log buffer,持久化到磁盤的過程,對應(yīng)的LSN分別是50、120 和160。

redo log 組提交

從圖中可以看到:
?①、trx1是第一個到達(dá)的,會被選為這組的 leader;
?②、等trx1要開始寫盤的時候,這個組里面已經(jīng)有了三個事務(wù),這時候LSN也變成了160;
?③、trx1去寫盤時,帶的是LSN=160,因此等trx1返回時,所有LSN小于等于160的redo log,都已經(jīng)被持久化到磁盤;
?④、這時候trx2和trx3就可以直接返回了。
??所以,一次組提交里面,組員越多,節(jié)約磁盤IOPS的效果越好。但如果只有單線程壓測,那就只能老老實實地一個事務(wù)對應(yīng)一次持久化操作了。在并發(fā)更新場景下,第一個事務(wù)寫完redo log buffer以后,接下來這個fsync越晚調(diào)用,組員可能越多,節(jié)約IOPS的效果就越好。為了讓一次fsync帶的組員更多,MySQL會優(yōu)化——拖時間。也就是在兩階段提交的時候,先把binlog從binlog cache中寫到磁盤上的binlog文件;再調(diào)用fsync持久化。
兩階段提交細(xì)化

<3>、提升binlog組提交的效果的方法:

??通過設(shè)置 binlog_group_commit_sync_delay 和 binlog_group_commit_sync_no_delay_count來實現(xiàn)。
?①、binlog_group_commit_sync_delay參數(shù),表示延遲多少微秒后才調(diào)用fsync;
?②、binlog_group_commit_sync_no_delay_count參數(shù),表示累積多少次以后才調(diào)用fsync。
??這兩個條件是或的關(guān)系,也就是說只要有一個滿足條件就會調(diào)用fsync。所以當(dāng)binlog_group_commit_sync_delay設(shè)置為0時,binlog_group_commit_sync_no_delay_count就無效了。

五、如果MySQL現(xiàn)在出現(xiàn)了性能瓶頸且瓶頸在IO上,此時的三種改進(jìn)方法:

?①、設(shè)置 binlog_group_commit_sync_delay 和 binlog_group_commit_sync_no_delay_count參數(shù),減少binlog的寫盤次數(shù)。這個方法是基于“額外的故意等待”來實現(xiàn)的,因此可能會增加語句的響應(yīng)時間,但沒有丟失數(shù)據(jù)的風(fēng)險。
?②、將sync_binlog 設(shè)置為大于1的值(比較常見是100~1000)。這樣做的風(fēng)險是,主機掉電時會丟binlog日志。
?③、將innodb_flush_log_at_trx_commit設(shè)置為2。這樣做的風(fēng)險是,主機掉電的時候會丟數(shù)據(jù)。

注意:不建議把innodb_flush_log_at_trx_commit 設(shè)置成0。因為把這個參數(shù)設(shè)置成0,表示redo log只保存在內(nèi)存中,這樣的話MySQL本身異常重啟也會丟數(shù)據(jù),風(fēng)險太大。而redo log寫到文件系統(tǒng)的page cache的速度也是很快的,所以將這個參數(shù)設(shè)置成2跟設(shè)置成0其實性能差不多,但這樣做MySQL異常重啟時就不會丟數(shù)據(jù)了,相比之下風(fēng)險會更小。

六、相關(guān)問題:

問題一:執(zhí)行一個update語句以后,再去執(zhí)行hexdump命令直接查看ibd文件內(nèi)容,為什么沒有看到數(shù)據(jù)有改變呢?

回答:這可能是因為WAL機制的原因。update語句執(zhí)行完成后,InnoDB只保證寫完了redo log、內(nèi)存,可能還沒來得及將數(shù)據(jù)寫到磁盤。

問題2:為什么binlog cache是每個線程自己維護(hù)的,而redo log buffer是全局共用的?

回答:MySQL這么設(shè)計的主要原因是,binlog是不能“被打斷的”。一個事務(wù)的binlog必須連續(xù)寫,因此要整個事務(wù)完成后,再一起寫到文件里。而redo log并沒有這個要求,中間有生成的日志可以寫到redo log buffer中。redo log buffer中的內(nèi)容還能“搭便車”,其他事務(wù)提交的時候可以被一起寫到磁盤中。

問題3:事務(wù)執(zhí)行期間,還沒到提交階段,如果發(fā)生crash的話,redo log肯定丟了,這會不會導(dǎo)致主備不一致呢?

回答:不會。因為這時候binlog 也還在binlog cache里,沒發(fā)給備庫。crash以后redo log和binlog都沒有了,從業(yè)務(wù)角度看這個事務(wù)也沒有提交,所以數(shù)據(jù)是一致的。

問題4:如果binlog寫完盤以后發(fā)生crash,這時候還沒給客戶端答復(fù)就重啟了。等客戶端再重連進(jìn)來,發(fā)現(xiàn)事務(wù)已經(jīng)提交成功了,這是不是bug?

回答:不是。實際上數(shù)據(jù)庫的crash-safe保證的是:
?①、如果客戶端收到事務(wù)成功的消息,事務(wù)就一定持久化了;
?②、如果客戶端收到事務(wù)失?。ū热缰麈I沖突、回滾等)的消息,事務(wù)就一定失敗了;
?③、如果客戶端收到“執(zhí)行異常”的消息,應(yīng)用需要重連后通過查詢當(dāng)前狀態(tài)來繼續(xù)后續(xù)的邏輯。此時數(shù)據(jù)庫只需要保證內(nèi)部(數(shù)據(jù)和日志之間,主庫和備庫之間)一致就可以了。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容