🎯 什麼情境該想到我
當你想理解(或自己實作)「系統當機/斷電後,資料如何不丟、狀態如何回復一致」時。
⚙️ 怎麼用
核心原則:先寫日誌,再改資料(Write-Ahead)。 只要日誌落盤,就算資料還沒寫完,重啟後也能靠日誌重建。
- 每次修改前,先把「要做什麼」順序追加寫入 WAL 並 fsync 落盤。
- 提交(commit)以日誌寫入成功為準,實際資料頁可稍後再刷。
- 崩潰復原(ARIES 三階段):分析 → 重做(redo 已提交但未落盤的)→ 撤銷(undo 未提交的)。
- WAL 也是複製與 CDC 的資料來源(把日誌串流給從庫)。
★ 為什麼「先寫日誌」就能保證不壞
根本問題:磁碟寫入不是原子的。 寫一個 8KB 的頁到一半斷電 → 半新半舊的頁,既不是舊狀態也不是新狀態。
❌ 直接改資料頁
改到一半斷電 → ★ 資料頁毀損,而且「不知道毀在哪」
✅ WAL
① 先「順序追加」寫日誌:「我要把 X 從 A 改成 B」
② fsync 讓日誌真的落盤
③ 回報 commit 成功 ← ★ 此刻就安全了
④ 資料頁稍後再刷(可以批次、可以延後)
斷電後重啟 → 讀日誌 → 重做已提交但沒刷完的,撤銷沒提交的
★ 關鍵洞察:WAL 把「隨機寫」變成「順序寫」。
資料頁散落在磁碟各處(隨機寫,慢);日誌只是往檔案尾端追加(順序寫,快得多)。
所以 WAL 不只讓資料安全,還讓寫入變快——這是它能成為所有資料庫標配的真正原因。
★ fsync 才是真正的瓶頸
write() → 資料只到了「作業系統的 page cache」 ★ 斷電就沒了
fsync() → 強制刷到「實體儲存」 ★ 這一步才安全,也是最慢的一步
| 儲存 | 單次 fsync 大約 |
|---|---|
| 機械硬碟 | ~10 ms(受限於轉速) |
| SSD | ~0.1–1 ms |
| 有電池保護的 RAID 卡快取 | ★ 極快(斷電時電池保住快取內容) |
★ 這解釋了資料庫的一個核心指標:「每秒能提交幾個交易」約等於「每秒能做幾次 fsync」。
★ 群組提交(group commit):把多個並行交易的日誌合併成一次 fsync——
這是資料庫提升寫入吞吐最重要的優化之一,也是「批次寫入比逐筆寫入快幾十倍」的原因。
⚠️ 廉價 SSD 會對 fsync 說謊(回報已落盤但其實還在自己的快取裡),斷電就毀。
生產環境要用有斷電保護(PLP)的企業級 SSD。
★ ARIES 三階段(崩潰復原的標準做法)
① 分析 Analysis:從最後一個 checkpoint 掃到日誌尾端
→ 找出崩潰時「哪些交易還沒提交」「哪些頁是髒的」
② 重做 Redo :★ 從 checkpoint 重放「所有」變更(包含未提交的)
→ 把資料庫還原到「崩潰當下」的狀態
③ 撤銷 Undo :★ 回捲所有未提交的交易
★ 「為什麼 redo 要包含未提交的」是經典追問:
因為要先把狀態精確還原到崩潰當下,才能正確地執行 undo
(undo 需要知道「當時的值」是什麼)。這叫 repeating history。Undo 本身也要寫日誌(CLR, compensation log record),否則 undo 到一半又崩潰就無法繼續。
Checkpoint 的作用:標記「這之前的都已經刷到資料頁了」→ 復原只要從 checkpoint 開始,不用從頭重放。
★ checkpoint 太稀疏 → 復原慢;太頻繁 → 平時的 I/O 壓力大。 這是可調的取捨。
★ WAL 的第二個身分:資料的變更串流
WAL 記錄了「每一次變更」,所以它天然就是一個有序的事件流:
| 用途 | 怎麼用 WAL |
|---|---|
| 複製 | 把 WAL 串流給從庫重放(見 工具-資料複製) |
| ★ CDC(變更資料捕捉) | Debezium 讀 PG 的邏輯解碼 / MySQL 的 binlog → 推到 Kafka |
| 時間點復原(PITR) | 基礎備份 + 重放到指定時間的 WAL |
| ★ 事件溯源 | 概念上同源:狀態是事件重放的結果 |
★ CDC 讀 WAL 而不是「定時輪詢資料庫」,是一個重要的架構選擇:
輪詢會漏掉中間狀態、有延遲、且對主庫造成負載;
讀 WAL 拿到的是完整、有序、低延遲的變更流。
→ 這也是 工具-Transactional-Outbox-Pattern 的一種替代方案。
同源的其他設計
| 系統 | 對應 |
|---|---|
| LSM-Tree | memtable 前的 WAL(見 工具-儲存引擎B-Tree與LSM-Tree) |
| 檔案系統 | ext4/XFS 的 journal |
| Kafka | ★ 整個系統就是一個 WAL(分散式的有序追加日誌) |
| Redis | AOF(append-only file) |
| etcd / Raft | ★ 共識演算法的 log 就是 WAL(見 工具-線性一致性與共識) |
| 備忘錄模式 | 檢查點 + 增量,同一個思路 |
🧪 我實際套用的紀錄
- 2026-07-15:(待填)
⚠️ 注意 / 什麼時候不適用
- ★
fsync的成本決定了寫入吞吐上限。想提高就要靠群組提交或批次交易,
而不是「加機器」——單一交易的提交延遲是物理限制。 - ★
synchronous_commit = off會讓寫入快很多,但斷電會丟掉最近的交易。
這是一個明確的取捨(適合可重算的資料,不適合金流)。要有意識地設定,不要當成「效能調校」隨手開。 - WAL 會佔空間且需要管理。PG 的
max_wal_size、歸檔策略設錯 →
★ 磁碟被 WAL 塞爆導致資料庫停止寫入(很常見的生產事故)。 - ★ 複製槽(replication slot)洩漏:從庫掛了但 slot 還在 → 主庫的 WAL 永遠不能回收 → 磁碟爆掉。
CDC 工具離線時特別容易踩到。要監控 slot 的落後量。 - 廉價 SSD 對 fsync 說謊(見上)。
- WAL 保證的是「單機的崩潰安全」,不是「磁碟毀損的安全」——那要靠備份與複製。
- 邏輯複製與實體複製不同:PG 的實體複製(streaming)要求版本一致;
邏輯複製(logical decoding)才能跨版本、跨系統——做 CDC 要用邏輯的。
🔗 相關工具
- 工具-儲存引擎B-Tree與LSM-Tree —— WAL 在兩種引擎裡的角色
- 工具-資料複製 —— WAL 串流就是複製
- 工具-交易與隔離等級 —— 提交的持久性由 WAL 保證
- 工具-Transactional-Outbox-Pattern —— CDC 是它的替代方案
- 工具-線性一致性與共識 —— Raft 的 log 也是 WAL
- 備忘錄模式 —— 檢查點 + 增量的同一個思路
- 工具-生產故障反模式清單 —— WAL 塞爆磁碟是經典事故
- 設計資料密集型應用、資料庫底層原理