程序員必知:MySQL為什么斷電后數(shù)據(jù)還能恢復(fù)?
了解MySQL的都知道,MySQL為了避免頻繁的磁盤IO拖累性能,設(shè)計了buffer機制。
這個機制本身沒什么問題,但仔細琢磨,你會發(fā)現(xiàn)一個有意思的細節(jié)。
MySQL的buffer中,每一頁是16K。而文件系統(tǒng)的一頁只有4K。
這意味著什么?
當(dāng)MySQL要把buffer里的一頁數(shù)據(jù)寫入磁盤時,實際上要操作文件系統(tǒng)里的4個頁才能完成。
圖片
看這張圖,MySQL里編號為1的頁,在物理磁盤上對應(yīng)著1、2、3、4這四個格子。寫入過程要經(jīng)歷四次操作,那如果寫到一半突然斷電了呢?這個問題就有點棘手了。
沒錯,這就是"頁數(shù)據(jù)損壞"。
想象一下這個場景:
MySQL正準備把page=1刷入磁盤,剛寫完前3個格子,電源就斷了。等系統(tǒng)重啟后,page=1對應(yīng)的磁盤位置上,數(shù)據(jù)是不完整的——只有前3個格子有新數(shù)據(jù),第4個格子還停留在舊狀態(tài)。
更麻煩的是,這種情況下連redo日志都救不回來,因為redo的前提是頁面數(shù)據(jù)本身是完整的。
那怎么辦?
最直接的想法就是:
能不能somewhere備份一個完整的副本?萬一出問題了,至少還能從副本恢復(fù)。
這個"somewhere"就是 Double Write Buffer(DWB) 。
DWB跟我們平時理解的buffer不太一樣。傳統(tǒng)buffer主要在內(nèi)存里,而DWB既有內(nèi)存部分,也有磁盤部分,數(shù)據(jù)必須落地存儲。
圖片
整個寫入流程是這樣的 :
- 當(dāng)某個頁需要刷盤時,首先會通過memcopy把數(shù)據(jù)復(fù)制到DWB的內(nèi)存區(qū)域。
- 接著,DWB內(nèi)存里的數(shù)據(jù)會先寫到DWB專用的磁盤空間。
- 最后,才把數(shù)據(jù)寫到真正的數(shù)據(jù)文件磁盤上。
DWB的容量其實不大,只有128個頁,總共2M。但這2M解決了大問題。
為什么DWB能防止頁損壞?
道理很簡單——數(shù)據(jù)寫了兩次,總有一個地方是好的。
如果第二步斷電了,沒關(guān)系,原本磁盤上1+2+3+4的完整數(shù)據(jù)還在,可以通過redo恢復(fù)。如果第三步斷電了,DWB里存著完整的備份,也能恢復(fù)。無論在哪個環(huán)節(jié)出問題,都不會真正丟失完整的頁數(shù)據(jù)。
我自己嘗試復(fù)現(xiàn)這個"頁損壞"場景好幾十次都沒成功,不過網(wǎng)上有人貼出過MySQL利用DWB修復(fù)頁數(shù)據(jù)的日志:
圖片
啟動日志里能看到,InnoDB檢測到上次是異常關(guān)閉,嘗試恢復(fù)ibd數(shù)據(jù)文件失敗了,然后就從DWB中把寫了一半的頁給恢復(fù)回來了。
不過話說回來,寫兩次磁盤,性能會不會受影響?
我們拆開看這三個步驟:
- 第一步memcopy到內(nèi)存,速度很快。
- 第二步從DWB內(nèi)存fsync到DWB磁盤,這是順序追加寫,也很快。
- 第三步隨機寫到數(shù)據(jù)盤,這本來就是必須的操作,不算額外開銷。
而且DWB的128頁數(shù)據(jù)會分兩次刷,每次最多64頁也就1M,執(zhí)行起來也挺快。
順序?qū)懙男阅軆?yōu)勢明顯,這也是write-ahead-log能提升性能的核心原因。
有第三方做過測評,DWB帶來的性能損失大概在10%左右,算是可以接受的代價。
想了解具體情況,可以查看InnoDB的兩個狀態(tài)變量:Innodb_dblwr_pages_written記錄寫入DWB的頁數(shù)量,Innodb_dblwr_writes記錄DWB的寫操作次數(shù)。執(zhí)行show global status like "%dblwr%"就能看到:
圖片
說到底,MySQL的數(shù)據(jù)安全機制設(shè)計得很周到。正常情況下,redo日志能搞定數(shù)據(jù)恢復(fù)。遇到頁損壞這種極端情況,double write buffer就派上用場了。DWB不只是個內(nèi)存buffer,它是內(nèi)存+磁盤的雙層結(jié)構(gòu),是InnoDB On-Disk架構(gòu)里不可或缺的一環(huán)。
通過寫兩次來保證頁面完整性,這個設(shè)計簡單卻實用。搞明白這個機制,對理解MySQL的可靠性保障很有幫助。




























