Doublewrite Buffer 是 InnoDB 為解決「頁斷裂」而設計的關鍵機制。本文從磁碟扇區、OS 頁面、資料庫頁面大小關係切入,推導頁斷裂成因,詳細剖析 Doublewrite 運作流程、Redo Log 格式差異、Oracle 替代方案與底層硬體層面的補充保護。
| 層級 | 典型大小 | 說明 |
|---|---|---|
| 資料庫頁面 | 16 KB (InnoDB 預設 innodb_page_size) | InnoDB 邏輯讀寫單位 |
| OS 頁面 | 4 KB (getconf PAGESIZE) | 作業系統記憶體管理單位 |
| IO 區塊 | 4 KB (blockdev --getbsz) | 邏輯磁碟 IO 單位,≤ OS 頁面 |
| 磁碟扇區 | 512 B / 4 KB (Advanced Format) | 物理磁碟最小寫入單位 |
關係:1 DB Page (16KB) = 4 OS Pages (4KB) = 4 IO Blocks = 32 Sectors (512B)
當資料庫寫入 16KB 頁面時,需拆分為 4 次 4KB OS 頁面寫入,再進一步拆分為扇區寫入。若在寫入過程中發生 **斷電、OS Crash、Kernel Panic**,可能導致 16KB 頁面**只有部分扇區寫入成功**,形成「半寫頁面」——即頁斷裂。
關鍵在於 **Redo Log 格式**:
| Redo 類型 | 格式 | 恢復依賴 | 受頁斷裂影響 |
|---|---|---|---|
| 邏輯日誌 | 操作語意 (INSERT ...) | 重放邏輯 | 否 |
| 物理日誌 | 具體位移寫入值 | 冪等重放 | 否 |
| 生理日誌 | 頁面級物理 + 頁內邏輯 | **頁面必須一致** | **是** ❌ |
InnoDB 採用 **生理日誌**:Redo 記錄「在頁面 offset X 寫入值 Y」,前提是頁面結構完整(頁頭、Slot、記錄鏈表指針正確)。若頁面斷裂,頁內結構錯亂,Redo 無法正確定位 offset,恢復中斷,資料庫無法啟動。
-- 共享表空間中固定分配 128 頁 (2MB) 為 Doublewrite Buffer
-- 刷新髒頁流程:
1. 從 Buffer Pool 取出多個髒頁 (最多 128 頁)
2. **順序寫入** Doublewrite Buffer (連續 2MB),呼叫 fsync() 確保落盤
3. 再將頁面寫入**實際資料檔案位置** (隨機 IO),呼叫 fsync()
-- 崩潰恢復檢查:
讀取 Doublewrite Buffer 中的頁面副本
對比實際資料檔案對應頁面
- 若 Doublewrite 頁面斷裂 → 丟棄 (不影響資料檔)
- 若資料檔頁面斷裂/不一致 → 從 Doublewrite 複製回覆
兩次寫入**時間點不同**,且 Doublewrite 是**順序寫入** (極快,單次 fsync),資料檔是**隨機寫入** (較慢)。機率上幾乎不可能在同一瞬間兩處都發生斷裂。
Oracle 同樣用生理日誌,**無 Doublewrite**,依賴:**備份 + 歸檔日誌**。
優點:恢復窗口可控;缺點:恢復時間長 (小時級),需額外儲存成本。
| 層級 | 技術 | 原理 | 能否徹底解決 |
|---|---|---|---|
| 檔案系統 | ZFS / Btrfs | Copy-on-Write + 校驗和,保證 4KB 區塊原子性 | 部分:保護 OS 頁面,但 16KB 跨 4 區塊仍可能斷裂 |
| RAID 控制器 | 帶電緩存 (BBWC/FBWC) | 斷電時緩存數據由電池/閃存保護,掉電後繼續寫入 | 大幅降低機率,但非 100% |
| 資料庫配置 | innodb_page_size=4KB | 與 OS 頁面對齊,單次 IO 寫入 | 理論可行,但增加頁面管理開銷,鮮少使用 |
-- 查看 Doublewrite 使用情況
SHOW ENGINE INNODB STATUS\G
# 輸出中:
# LOG
# ...
# Doublewrite buffer: 128 pages, 0% used
-- 關閉 Doublewrite (極不建議,僅測試)
[mysqld]
innodb_doublewrite = 0
-- 頁面大小調整 (僅初始化時指定,不可變更)
[mysqld]
innodb_page_size = 4096 # 或 8192, 16384(default)