1. 程式人生 > >23 mysql怎麼保證資料不丟失?

23 mysql怎麼保證資料不丟失?

MySQLwal機制,得到的結論是:只要redo logbinlog 持久化到磁碟,就能確保mysql異常重新啟動後,資料是可以恢復的。

binlog的寫入機制

其實,binlog的寫入邏輯比較簡單:事務執行過程中,先把日誌寫到binlog cache,事務提交的時候,再把binlog cache的內容寫到binlog檔案中

一個事務的binlog不能被拆開的,因此不論事務多大,也要確保一次性寫入,這就涉及到binlog cache的儲存問題。

系統給binlog cache分配了一片記憶體,每個執行緒一個,引數binlog_cache用於控制單個執行緒內binlog cache所佔記憶體的大小,如果超過這個值,就要

暫存磁碟

事務提交的時候,執行器把binlog cache的完整事務寫入到binlog 檔案中,並清空binlog cache,狀態如圖

可以看到每個執行緒都自己的binlog cache,但是公用一個binlog檔案。

圖中的write就是把日誌寫入到檔案系統中的page cache,並沒有把資料持久化到磁碟,所以速度比較快

圖中的fsync,將資料持久化到磁碟的操作,一般情況下,我們認為fsync才是佔磁碟的IOPS

writefsync由引數sync_binlog控制

1 sync_binlog=0 表示每次提交的時候事務都只write,不fsync

2 sync_binlog=1 表示事務每次提交都會fsync到磁碟

3 sync_binlog=N(N>1)表示每次提交事務都會write,但累計N個事務後才fsync

因此,在出現io瓶頸的情況下,可以調整sync_binlog的值偏大一點,可以提升效能,但是在實際業務場景中,考慮到丟失日誌量的可控性,一般不建議將該值設定為0,比較常見的是設定為100~1000中的某個數值。

但是,將sync_binlog設定為N,對應的風險是:如果主機發生異常重啟,將丟失N個事務的binlog 日誌。

redo log的寫入機制

事務在執行過程中,生成的redo log是要先寫到redo log buffer,那

redo log buffer的內容是不是每次生成後都要直接持久化到磁碟呢?答案是不需要,

如果事務執行期間,mysql發生異常重啟,那麼這部分日誌就丟失了,由於事務還沒有提交,丟失的就不影響。

那麼在事務還沒有提交的時候,redo log buffer中的部分日誌會不會持久化到磁碟呢?答案是會有的,這個要從redo log的三種狀態說起

三種狀態分別是:

1 存在redo log buffer,物理上就是mysql的程序記憶體中,紅色部分。

2 寫到磁碟(write),但是沒有持久化(fsync)到磁碟物理上就是作業系統的page cache,黃色的部分。

3 持久化到磁碟,對應的是hard disk,圖中綠色的部分。

日誌寫到redo log buffer是很快的,write寫到page cache也很快,但是持久化的速度就相對要慢很多。為了控制redo log的寫入策略,innodb提供了引數innodb_fush_log_at_trx_commit,它有三種取值:

1 設定為0的時候,表示每次事務提交都會只把redo log留在redo log buffer

2 設定為1的時候,事務的每次提交都會持久化到磁碟

3 設定為2的時候,事務的每次提交都會把redo log 寫入到page cache

Innodb有一個後臺執行緒,1就會把redo log buffer中的寫入到redo log,呼叫write寫入系統的page cache,然後呼叫fsync持久化到磁碟。

注意,事務執行過程中的redo log也是直接寫在redo log buffer中的,這些redo log也會被後臺執行緒一起持久化到磁碟,也就是說,一個沒有提交的事務的redo log,也可能會已經持久化到了磁碟的。

實際上,除了後臺執行緒每秒一次的輪詢操作,還有場景讓沒有提交的事務的redo log寫入到磁碟中。

1 redo log buffer佔用的空間即將達到innodb_log_buffer_size的一半,後臺執行緒會主動寫盤。注意,由於這個事務並沒有提交,所以這個寫盤動作只是write,而沒有呼叫fsync,也就是隻留在了檔案系統page cache中。

2 並行的事務提交的時候,順帶將這個redo log buffer持久到了磁碟。假設事務A執行到一半,已經寫了一些redo log buffer,這時候另外一個執行緒的事務B提交,如果innodb_flush_log_at_trx_commit設定為1,那麼按照這個引數的邏輯,事務B要把redo buffer裡的日誌全部持久化到磁碟,這時候,就會帶上事務Aredo log buffer的日子一起持久化到磁碟。

兩階段提交的時候,時序上redo logprepare,在寫binlog,最後再把redo log commit

如果把引數innodb_flush_log_at_trx_commit設定為1,那麼redo logprepare階段就要持久化一次,因為有一個崩潰恢復邏輯是要依賴於prepareredo log,再加上binlog來恢復。

每秒一次的後臺輪詢刷盤,再加上崩潰恢復的這個邏輯,innodb就認為redo logcommit的時候就不需要fsync了,只會write到檔案系統的page cache中就夠了。

通常我們說的”1”模式,就是把引數sync_binloginnodb_flush_log_at_trx_commit都設定為1。也就說,一個事務完整提交前,需要等待兩次刷盤,一次是redo log(prepare)階段,一次是binlog

這時候,如果看到mysqltps2w的話,每秒就會寫4w次磁碟,但是,用工具測試,磁碟能力也就2w左右,怎麼實現tps2w呢?

要解釋這個問題,就要用到組提交(group commit)機制了

日誌的邏輯序列號(LSN)是單調遞增的,用來對應redo log的一個個寫入點,每次寫入的長度為lengthredo logLSN就會加上length

LSN也會寫到innodb的資料頁中,來確保資料不會被多次執行重複的redo log,關於lsnredo logcheckpoint後面會提到。

如圖所示,是三個併發事務(trx1,trx2,trx3)prepare階段,都寫完redo log buffer,持久化到磁碟的過程,對應的LSN分別是50,120,160

從圖中看到

1 trx1是第一個到達,會被選為這個組的leader

2 trx1要開始寫盤的時候,這個組裡面已經有了三個事務,這時候LSN變成了160

3 trx1去寫盤的時候,帶的就是lsn=160,因此等trx1返回時,所有lsn小於160redo log都已經持久化到磁碟

4 這時候trx2trx3就可以直接返回了。

所以,一次組提交裡面,組員越多,節約磁碟的iops的效果就越好,但如果只是單執行緒壓測,那就還是一個事務對應一次持久化操作了。

在併發更新的場景下,第一個事務寫完redo log buffer以後,接下來這個fsync越晚呼叫,組員就能越多,節約的iops效果就越好。

為了讓一次fsync帶的組員更多,mysql有一個優化,託時間。

圖中,把寫binlog當成一個動作,但實際上寫binlog分為兩步

1 先把binlogbinlog cache中寫道磁碟上的binlog檔案

2 呼叫fsync持久化

Mysql為了讓組提交的效果更好,把redo logfsync的時間拖到了步驟1之後,上圖變成如下

 

這麼一來,binlog也可以組提交了,在執行上圖的第四步把binlog fsync到磁碟時,如果有多個事務的binlog已經寫完了,也是一起持久化的,這樣也可以減少iops的消耗。

不過通常情況下第3步執行得會很快,所以binlogwritefsync間的間隔很短,導致能合到一起持久化的binlog比較少,因此binlog的組提交的效果通常不如redo log的效果那麼好。

如果想提升binlog的組提交效果,可以通過設定(5.7)binlog_group_commit_sync_delay和binlog_group_commit_sync_no_delay_count來實現

1 binlog_group_commit_sync_delay引數,表示延遲多少微妙後才呼叫fsync

2 binlog_group_commit_sync_no_delay_count 引數,表示累計多少次後才呼叫fsync

這兩個條件是或的關係,也就說只要滿足其中一個就會呼叫fsync

binlog_group_commit_sync_delay0的時候,另外的引數也就無效了。

WAL機制主要得益於兩個方面

1 redo logbinlog都是順序寫,磁碟的順序寫比隨機寫速度快。

2 組提交機制,可以大幅度降低磁碟的iops的消耗。

到這裡,如果mysql出現了效能瓶頸,而且瓶頸在io上,可以考慮呢些方法來提升

1 設定binlog_group_commit_sync_delaybinlog_group_commit_sync_no_delay_count引數,減少binlog的寫盤次數,這個方法是基於”額外的故意等待來實現”,因此可能會增加語句返回的響應時間,但是沒有丟失資料的風險。

2 sync_binlog設定為大於1的值(100~1000),這樣做的風險是,主機掉電會丟失binlog日誌。

3 innodb_flush_log_at_trx_commit=2,這樣做的風險是,主機掉電就丟失資料。

不建議把innodb_flush_log_at_trx_commit設定為0,如果=0的話,redo只儲存在記憶體中,這樣的話,mysql本身異常重啟也會丟失資料,風險太大,

redo log寫到檔案系統的page cache的速度也是很快,所以這個引數設定為20的效能差不多,但是,這樣2就不會在mysql異常重啟時丟失資料。

小結

如果保證binlogredo log是完整的,就可以保證mysqlcrash-safe的。

問題1,執行一個update語句以後,再去執行hexdump命令直接檢視idb檔案,為什麼沒有看到資料改變?

回答:這可能是因為WAL機制的原因,update語句執行完成,innodb只保證redolog,記憶體,可能還沒來得及將資料寫到磁碟。

問題2,為什麼binlog cache每個執行緒自己維護,而redo log buffer是全域性公用?

回答:mysql這麼設計,binlog是不能”被打斷”一個事務的binlog必須連續寫,因此要整個事務完成後,再一起寫入到檔案裡。

redo log沒有這個要求,中間生成的日誌可以寫到redo log buffer中,還可以被寫到磁碟中。

問題3,事務執行期間,還沒到提交階段,如果發生crashredo log肯定丟失,這會不會導致主備不一致。

回答:不會,這時候binlog還在binlog cache裡,沒發給備庫,carsh後,redo logbinlog都沒有,從業務角度看事務還沒有提交,所以資料是一致的。

問題4,如果binlog寫完後發生crash,這時候還沒有給客戶端答覆就重啟了,等客戶端重新連線進來,發現事務已經提交成功,這是不是bug

回答:不是,

假設整個事務提交成功了,redo log commit完成了,備庫也收到binlog並執行了,但是主庫和客戶端網路斷開了,導致事務成功的包返回不了,這時候客戶端收到”網路斷開”異常,這種也是算事務成功的,不能認為是bug

實際上資料庫的crash-safe保證的是:

1 如果客戶端收到事務成功的訊息,事務就一定持久化了

2 如果客戶端收到失敗(比如pk衝突,rollback等)的訊息,事務就一定失敗了

3 如果客戶端收到”執行異常”的訊息,應用需要重連通過當前查詢來繼續後續的邏輯,此時資料庫只需要保證內部(資料和日誌之間,主庫和備庫之間)一致就可以了。