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