1. 程式人生 > >23 mysql怎麽保證數據不丟失?

23 mysql怎麽保證數據不丟失?

數據 序列號 提升性能 disk 邏輯 length 問題 繼續 ali

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 如果客戶端收到”執行異常”的消息,應用需要重連通過當前查詢來繼續後續的邏輯,此時數據庫只需要保證內部(數據和日誌之間,主庫和備庫之間)一致就可以了。

23 mysql怎麽保證數據不丟失?