深入解析 MySQL 雙寫(xiě)緩沖區(qū)
在數(shù)據(jù)庫(kù)系統(tǒng)的世界中,保障數(shù)據(jù)的完整性和穩(wěn)定性是至關(guān)重要的任務(wù)。為了實(shí)現(xiàn)這一目標(biāo),MySQL內(nèi)部使用了許多精巧而高效的機(jī)制。
InnoDB是MySQL中一種常用的事務(wù)性存儲(chǔ)引擎,它具有很多優(yōu)秀的特性。其中,Doublewrite Buffer是InnoDB的一個(gè)重要特性之一,本文將介紹Doublewrite Buffer的原理和應(yīng)用,幫助讀者深入理解其如何提高M(jìn)ySQL的數(shù)據(jù)可靠性并防止可能的數(shù)據(jù)損壞。
為什么需要Doublewrite Buffer
我們常見(jiàn)的服務(wù)器一般都是Linux操作系統(tǒng),Linux文件系統(tǒng)頁(yè)(OS Page)的大小默認(rèn)是4KB。而MySQL的頁(yè)(Page)大小默認(rèn)是16KB。
可以使用如下命令查看MySQL的Page大?。?/p>
SHOW VARIABLES LIKE 'innodb_page_size';
一般情況下,其余程序因?yàn)樾枰僮飨到y(tǒng)交互,所以它們的頁(yè)(Page)大小都為操作系統(tǒng)頁(yè)大小的整數(shù)倍。比如,Oracle的Page大小為8KB。
MySQL程序是跑在Linux操作系統(tǒng)上的,理所當(dāng)然要跟操作系統(tǒng)交互,所以MySQL中一頁(yè)數(shù)據(jù)刷到磁盤(pán),要寫(xiě)4個(gè)文件系統(tǒng)里的頁(yè)。
如圖所示:
需要注意的是,這個(gè)刷頁(yè)的操作并非原子操作,比如我操作系統(tǒng)寫(xiě)到第二個(gè)頁(yè)的時(shí)候,Linux機(jī)器斷電了,這時(shí)候就會(huì)出現(xiàn)問(wèn)題了。造成「頁(yè)數(shù)據(jù)損壞」。并且這種頁(yè)數(shù)據(jù)損壞靠 redo日志是無(wú)法修復(fù)的。
redo重做日志中記錄的是對(duì)頁(yè)的物理操作,而不是頁(yè)面的全量記錄,當(dāng)發(fā)生「Partial Page Write(部分頁(yè)寫(xiě)入)」問(wèn)題時(shí),出現(xiàn)問(wèn)題的是未修改過(guò)的數(shù)據(jù),此時(shí)redo日志無(wú)能為力。
Doublewrite Buffer的出現(xiàn)就是為了解決上面的這種情況,給InnoDB存儲(chǔ)引擎提供了數(shù)據(jù)頁(yè)的可靠性,雖然名字帶了Buffer,但實(shí)際上Doublewrite Buffer是「內(nèi)存+磁盤(pán)」的結(jié)構(gòu)。
- 內(nèi)存結(jié)構(gòu):Doublewrite Buffer內(nèi)存結(jié)構(gòu)由128個(gè)頁(yè)(Page)構(gòu)成,大小是2MB。
- 磁盤(pán)結(jié)構(gòu):Doublewrite Buffer磁盤(pán)結(jié)構(gòu)在系統(tǒng)表空間上是128個(gè)頁(yè)(2個(gè)區(qū),extend1和extend2),大小是2MB。
Doublewrite Buffer的原理是,再把數(shù)據(jù)頁(yè)寫(xiě)到數(shù)據(jù)文件之前,InnoDB先把它們寫(xiě)到一個(gè)叫「doublewrite buffer(雙寫(xiě)緩沖區(qū))」的共享表空間內(nèi),在寫(xiě)doublewrite buffer完成后,InnoDB才會(huì)把頁(yè)寫(xiě)到數(shù)據(jù)文件適當(dāng)?shù)奈恢谩?/p>
如果在寫(xiě)頁(yè)的過(guò)程中發(fā)生意外崩潰,InnoDB會(huì)在doublewrite buffer中找到完好的page副本用于恢復(fù)。
Doublewrite Buffer原理
如上圖所示,當(dāng)有數(shù)據(jù)頁(yè)要刷盤(pán)時(shí):
- 頁(yè)數(shù)據(jù)先通過(guò)memcpy函數(shù)拷貝至內(nèi)存中的Doublewrite Buffer中。
- Doublewrite Buffer的內(nèi)存里的數(shù)據(jù)頁(yè),會(huì)fsync刷到Doublewrite Buffer的磁盤(pán)上,分兩次寫(xiě)入磁盤(pán)共享表空間中(連續(xù)存儲(chǔ),順序?qū)懀阅芎芨?,每次寫(xiě)1MB。
- Doublewrite Buffer的內(nèi)存里的數(shù)據(jù)頁(yè),再刷到數(shù)據(jù)磁盤(pán)存儲(chǔ).ibd文件上(離散寫(xiě))。
如果操作系統(tǒng)在將頁(yè)寫(xiě)入磁盤(pán)的過(guò)程中發(fā)生了崩潰,在恢復(fù)過(guò)程中,InnoDB存儲(chǔ)引擎可以從共享表空間中的Double write中找到該頁(yè)的一個(gè)副本,將其復(fù)制到表空間文件,再應(yīng)用redo日志。
所以在正常的情況下,MySQL寫(xiě)數(shù)據(jù)頁(yè)時(shí),會(huì)寫(xiě)兩遍到磁盤(pán)上,第一遍是寫(xiě)到doublewrite buffer,第二遍是寫(xiě)到真正的數(shù)據(jù)文件中,這便是「Doublewrite」的由來(lái)。
我們可以通過(guò)如下命令來(lái)監(jiān)控Doublewrite Buffer工作負(fù)載,該命令用于顯示有關(guān)雙寫(xiě)緩沖區(qū)(doublewrite buffer)的統(tǒng)計(jì)信息。'%dblwr%' 是一個(gè)通配符,匹配所有包含 'dblwr' 的狀態(tài)變量。
show global status like '%dblwr%';
這個(gè)命令可能會(huì)產(chǎn)生如下格式的輸出:
+------------------------+-------+
| Variable_name | Value |
+------------------------+-------+
| Innodb_dblwr_writes | 1000 |
| Innodb_dblwr_pages_written | 8000 |
+------------------------+-------+
Doublewrite Buffer和redo log
在MySQL的InnoDB存儲(chǔ)引擎中,Redo log和Doublewrite Buffer共同工作以確保數(shù)據(jù)的持久性和恢復(fù)能力。
- 當(dāng)有一個(gè)DML(如INSERT、UPDATE)操作發(fā)生時(shí), InnoDB會(huì)首先將這個(gè)操作寫(xiě)入redo log(內(nèi)存)。這些日志被稱為未檢查點(diǎn)(uncheckpointed)的redo日志。
- 然后,在修改內(nèi)存中相應(yīng)的數(shù)據(jù)頁(yè)之前,需要將這些更改記錄在磁盤(pán)上。但是直接把這些修改的頁(yè)寫(xiě)到其真正的位置可能會(huì)因發(fā)生故障導(dǎo)致頁(yè)部分更新,從而導(dǎo)致數(shù)據(jù)不一致。因此,InnoDB的做法是先將這些修改的頁(yè)按順序?qū)懭雂oublewrite buffer。這就是為什么叫做 "doublewrite" —— 數(shù)據(jù)實(shí)際上被寫(xiě)了兩次,先在doublewrite buffer,然后在它們真正的位置。
- 一旦這些頁(yè)被安全地寫(xiě)入doublewrite buffer,它們就可以按原始的順序?qū)懟氐轿募到y(tǒng)中。即使這個(gè)過(guò)程在寫(xiě)回?cái)?shù)據(jù)時(shí)發(fā)生故障,我們?nèi)匀豢梢詮膁oublewrite buffer中恢復(fù)數(shù)據(jù)。
- 最后,當(dāng)事務(wù)提交時(shí),相關(guān)聯(lián)的redo log會(huì)被寫(xiě)入磁盤(pán)。這樣即使系統(tǒng)崩潰,redo log也可以用來(lái)重播(replay)事務(wù)并恢復(fù)數(shù)據(jù)庫(kù)。
在系統(tǒng)恢復(fù)期間,InnoDB會(huì)檢查doublewrite buffer,并嘗試從中恢復(fù)損壞的數(shù)據(jù)頁(yè)。如果doublewrite buffer中的數(shù)據(jù)是完整的,那么InnoDB就會(huì)用doublewrite buffer中的數(shù)據(jù)來(lái)更新?lián)p壞的頁(yè)。否則,如果doublewrite buffer中的數(shù)據(jù)不完整,InnoDB也有可能丟棄buffer內(nèi)容,重新執(zhí)行那條redo log以嘗試恢復(fù)數(shù)據(jù)。
所以,Redo log和Doublewrite Buffer的協(xié)作可以確保數(shù)據(jù)的完整性和持久性。如果在寫(xiě)入過(guò)程中發(fā)生故障,我們可以從doublewrite buffer中恢復(fù)數(shù)據(jù),并通過(guò)redo log來(lái)進(jìn)行事務(wù)的重播。
Doublewrite Buffer相關(guān)參數(shù)
以下是一些與Doublewrite Buffer相關(guān)的參數(shù)及其含義:
- innodb_doublewrite:這個(gè)參數(shù)用于啟用或禁用雙寫(xiě)緩沖區(qū)。設(shè)置為1時(shí)啟用,設(shè)置為0時(shí)禁用, 默認(rèn)值為1。
- innodb_doublewrite_files:這個(gè)參數(shù)定義了多少個(gè)雙寫(xiě)文件被使用。默認(rèn)值為2,有效范圍從2到127。
- innodb_doublewrite_dir:這個(gè)參數(shù)指定了存儲(chǔ)雙寫(xiě)緩沖文件的目錄的路徑。默認(rèn)為空字符串,表示將文件存儲(chǔ)在數(shù)據(jù)目錄中。
- innodb_doublewrite_batch_size: 這個(gè)參數(shù)定義了每次批處理操作寫(xiě)入的字節(jié)數(shù)。默認(rèn)值為0,表示InnoDB會(huì)選擇最佳的批量大小。
- innodb_doublewrite_pages:這個(gè)參數(shù)定義了每個(gè)雙寫(xiě)文件包含多少頁(yè)面。默認(rèn)值為128。
總結(jié)
Doublewrite Buffer是InnoDB的一個(gè)重要特性,用于保證MySQL數(shù)據(jù)的可靠性和一致性。
它的實(shí)現(xiàn)原理是通過(guò)將要寫(xiě)入磁盤(pán)的數(shù)據(jù)先寫(xiě)入到Doublewrite Buffer中的內(nèi)存緩存區(qū)域,然后再寫(xiě)入到磁盤(pán)的兩個(gè)不同位置,來(lái)避免由于磁盤(pán)損壞等因素導(dǎo)致數(shù)據(jù)丟失或不一致的問(wèn)題。
總的來(lái)說(shuō),Doublewrite Buffer對(duì)于改善數(shù)據(jù)庫(kù)性能和數(shù)據(jù)完整性起著至關(guān)重要的作用。盡管其引入了一些開(kāi)銷,但在大多數(shù)情況下,這些成本都被其提供的安全性和可靠性所抵消。