目錄
- 什麼是 WAL
- 為什麼需要 WAL
- 兩條核心規則
- redo log 與 undo log
- LSN 與髒頁追蹤
- 崩潰復原:ARIES 三階段
- checkpoint 的角色
- WAL vs rollback journal
- WAL 的額外紅利:複寫 / PITR / CDC
- 各資料庫的實作對照
- 常見問題
- 總結與速查
什麼是 WAL
預寫日誌(Write-Ahead Logging, WAL) 的核心規則只有一句:先寫日誌,再改資料。任何對資料頁的修改,都必須先把「這次改了什麼」寫成一筆 log record 並確保它落到穩定儲存(磁碟),資料頁本身才可以(稍後)寫回。
它要同時達成兩個看似衝突的目標:
- 持久性(Durability):交易一旦 commit,即使立刻斷電也不能丟。
- 效能:不能為了持久性,讓每次 commit 都去做大量緩慢的隨機寫。
WAL 的解法是把「貴的事」和「必須做的事」拆開:commit 時只需要把該交易的 log(循序寫、便宜)確保落地;被改動的資料頁可以留在記憶體當髒頁,稍後由 checkpoint 批次寫回。
名詞歧義提醒:嚴格說「WAL」只指上面那條「先寫日誌」的規則;但各家常把整套機制或特定模式也叫 WAL——PostgreSQL 直接把它的 redo 日誌叫
WAL,SQLite 有一個叫「WAL 模式」的 journaling 模式。看到「WAL」時要分清指的是規則還是某產品的具體實作。
為什麼需要 WAL
考慮「交易 commit 當下,資料到底要不要立刻寫回磁碟」這個問題,沒有 WAL 時會陷入兩難:
| 做法 | 問題 |
|---|---|
| commit 時強制把所有被改的資料頁寫回磁碟 | 資料頁散落各處 → 每次 commit 一堆隨機寫,慢;且寫到一半崩潰,資料頁處於半新半舊,破壞原子性 |
| commit 時什麼都不特別做(靠 OS 慢慢刷) | commit 說成功了,但變更還在記憶體,斷電就丟資料(不持久);未提交的變更若已被刷回也無法撤銷(不原子) |
WAL 用一條循序寫入、成本低的日誌當「事實來源」化解:
交易改資料
→ ① 先把改動寫成 log record(循序 append 到日誌)
→ ② commit:只要該交易的 log 確實落地,就算持久了
→ ③ 資料頁留在記憶體當髒頁,稍後 checkpoint 批次寫回(隨機寫被延後 + 攤提)
崩潰後靠日誌重建:已 commit 但資料頁沒寫回的 → 用日誌重做(redo);未 commit 卻已被寫回的 → 用日誌撤銷(undo)。於是資料頁怎麼刷、何時刷都不影響正確性,才能安心延後、批次、循序化。
兩條核心規則
WAL 其實是兩條不變式(invariant)合起來,各自保證「能 undo」與「能 redo」:
- Undo 規則(狹義的 write-ahead rule):某個髒頁被寫回磁碟之前,描述該修改的 log record(含 undo 資訊)必須先落地。
- 保證:就算未提交的髒頁已經被寫進磁碟,崩潰後也還有 log 可以把它撤銷回去。
- Redo 規則(force-log-at-commit):交易 commit 之前,它所有的 log record 必須先落地。
- 保證:就算已提交交易的資料頁還沒寫回,崩潰後也能用 log 把它重做補上。
這兩條規則對應緩衝區管理的兩個策略,也是為什麼 redo/undo 都需要:
| 策略 | 意思 | 需要哪種 log |
|---|---|---|
| Steal(允許偷取) | 允許把「未提交」交易的髒頁寫回磁碟(好騰出記憶體) | 需要 undo(才能事後撤銷) |
| No-Force(提交不強制) | commit 時不強制把資料頁全部寫回 | 需要 redo(才能事後補做) |
主流資料庫(含 SQL Server、ARIES 系統)都採 steal + no-force——這是效能最好的組合,代價就是 redo 與 undo 兩者都要。
redo log 與 undo log
| redo log | undo log | |
|---|---|---|
| 記什麼 | 修改後的新值 / 如何重做 | 修改前的舊值 / 如何撤銷 |
| 用在 | 崩潰後把「已提交但沒落地」的變更補回 | 回滾(ROLLBACK)、崩潰後撤銷未提交交易;也常供 MVCC 讀取舊版本 |
| 對應規則 | Redo 規則(no-force 需要它) | Undo 規則(steal 需要它) |
有些系統把兩者放在同一條 log 流(如 SQL Server 的 .ldf、Oracle 的 redo 內含變更向量),有些分開(如 MySQL InnoDB 的 redo log 與 undo log 各自獨立)。PostgreSQL 則特別:它有 redo(WAL),但沒有獨立的 undo log——舊版本直接留在資料表裡(MVCC),回滾只是標記、之後由 VACUUM 清理(見對照表)。
LSN 與髒頁追蹤
WAL 靠幾個編號把「日誌」與「資料頁」對起來,才知道崩潰後哪些要重做:
- LSN(Log Sequence Number):每筆 log record 一個單調遞增的序號,等於它在日誌流中的位置。
- pageLSN:每個資料頁上記著「最後一次修改它的那筆 log record 的 LSN」。
- recLSN:某髒頁「第一次被弄髒(還沒刷回)」的那筆 log 的 LSN。
判斷某筆 log 要不要對某頁 redo,就是比大小:
若 log record 的 LSN > 該頁磁碟上的 pageLSN
→ 這次修改還沒反映到磁碟頁上 → 需要 redo
否則
→ 磁碟頁已經比這筆 log 新 → 這筆已套用過,跳過(redo 的冪等性)
這個比對讓 redo 可以重放同一段 log 多次都安全(例如 redo 到一半又崩潰再來一次),是崩潰復原能可靠運作的關鍵。
崩潰復原:ARIES 三階段
ARIES 是最經典的 WAL 崩潰復原演算法(SQL Server、DB2 等都屬這一系)。重啟後分三個階段,從最後一個 checkpoint 出發:
崩潰 → 重啟復原
① Analysis(分析):從最後 checkpoint 往後掃 log,重建兩張表
- Dirty Page Table:崩潰當下有哪些髒頁、各自的 recLSN
- Transaction Table:崩潰當下有哪些「還沒結束」的交易
→ 由髒頁表中最小的 recLSN 算出 redo 的起點 RedoLSN
② Redo(重做):從 RedoLSN 起「重演歷史」(repeating history),
把所有記錄過的變更(不管已提交或未提交)依 LSN 重放,
用 pageLSN 比對跳過已套用的 → 資料庫回到「崩潰當下」的狀態
③ Undo(復原):把崩潰時仍未提交的交易,依 LSN 反向逐一回滾;
回滾動作本身也寫成 CLR(補償記錄),使 undo 可重入、崩潰可續做
三個要點常被考:
- 先 redo 再 undo:ARIES 刻意「先把歷史完整重演一遍(含未提交的),再回滾未提交的」。這比「只 redo 已提交的」更單純可靠,能一致處理各種交錯情況。
- CLR(Compensation Log Record,補償記錄):undo 時把「我撤銷了什麼」也寫進 log。這樣即使在 undo 過程中又崩潰,重來時不會把同一筆重複撤銷。
- 冪等:靠 LSN 比對,redo/undo 重放幾次都得到相同結果。
checkpoint 的角色
如果每次崩潰都得從日誌最開頭開始 redo,復原會慢到不可用。checkpoint 定期把髒頁刷回磁碟、並在日誌記下一個檢查點,作用是縮短崩潰復原的起點:
- 把髒頁寫回 → 抬高「最舊未落地變更」的位置 → redo 起點(RedoLSN)往後移。
- 復原時只需從最後一個 checkpoint 掃起,而不是掃整個歷史。
代價是 checkpoint 本身要做 I/O,所以是「復原速度 vs 平時 I/O 負擔」的取捨。現代做法多是持續小量刷髒頁的間接 checkpoint(如 SQL Server 的 TARGET_RECOVERY_TIME、PostgreSQL 的 checkpoint_completion_target),把尖峰打平。
WAL vs rollback journal
「崩潰時如何保證原子+持久」有兩條主流路線,WAL(redo 式)只是其一:
| Rollback journal(undo 式) | WAL(redo 式) | |
|---|---|---|
| 做法 | 改資料前先把舊頁備份到 journal,直接改原資料檔 | 改動先寫進 log,原資料檔稍後才由 checkpoint 更新 |
| commit | 刪掉 journal 即完成(但改動已在原檔,需 fsync 原檔) | 只需把 log 落地即完成 |
| 崩潰復原 | 用 journal 的舊頁還原原檔 | 用 log 重做未落地的變更 |
| 併發 | 寫時通常需獨占(讀寫互斥) | 讀可與寫並行(讀舊檔、寫進 log) |
| 代表 | SQLite 傳統 rollback journal 模式、早期做法 | PostgreSQL、MySQL InnoDB、SQL Server、SQLite WAL 模式 |
WAL 之所以成為主流,關鍵在commit 只碰循序的 log、且讀寫併發度好;rollback journal 的 commit 要動到散落的原資料檔、併發也差。(SQLite 官方文件對這兩種模式的對比是很好的實例。)
WAL 的額外紅利:複寫 / PITR / CDC
WAL 一旦存在,它就成了「資料庫發生過什麼」的完整、有序事實流。於是很多功能都直接讀這條 log 來實現,而不必另建機制:
- 時間點還原(PITR):完整備份 + 一連串 log 備份接力,就能還原到任意時刻。
- 複寫 / 高可用:把 log 送到另一台重放,就得到一份副本(PostgreSQL streaming replication、MySQL 的 redo/ binlog、SQL Server Always On / 交易式複寫都圍繞 log)。
- CDC(變更資料擷取):讀 log 把每筆變更抽出來送給下游。
反過來,這也解釋了一個維運現象:只要有下游還沒讀完 log,這段 log 就不能被回收,記錄檔因此變大。(SQL Server 的表現就是 log_reuse_wait_desc = REPLICATION,詳見 SQL Server 交易記錄檔管理。)
各資料庫的實作對照
| 資料庫 | redo(重做 / 持久性) | undo(撤銷 / 回滾) | 備註 |
|---|---|---|---|
| PostgreSQL | WAL(pg_wal) |
無獨立 undo log——舊版本留在表中(MVCC),回滾靠標記、VACUUM 清理 | 用 full-page writes 防 torn page |
| MySQL InnoDB | redo log(redo log files) | undo log(undo tablespace),兼供 MVCC 讀視圖 | redo 與 undo 分離 |
| SQL Server | 交易記錄檔 .ldf(redo 與 undo 同一條流) |
同左 | 復原模式控制 log 保留 |
| Oracle | redo log + archive log | undo tablespace(undo segments) | 概念近似 InnoDB |
| SQLite | WAL 模式(-wal 檔,redo 式) |
rollback journal 模式(-journal,undo 式) |
兩種日誌模式擇一 |
觀念相通、名詞各異:
.ldf/pg_wal/ redo log /-wal檔本質都是「預寫日誌」。差別主要在 redo 與 undo 是否同流、undo 是走獨立日誌還是靠 MVCC、以及保留與回收策略。
常見問題
問題 1:WAL 就是 redo log 嗎?
不完全等價。WAL 是「先寫日誌再改資料」的規則;redo log 是為了滿足其中「Redo 規則」而記錄的日誌內容。口語上大家常互稱,但嚴格講 WAL 這套協定同時需要 redo 和 undo 才完整(PostgreSQL 是把 undo 用 MVCC 代替的特例)。
問題 2:為什麼 redo 要連「未提交的交易」也重做?
這是 ARIES 的「重演歷史」原則。先把資料庫還原到崩潰當下的完整狀態(包含未提交的變更),再統一把未提交的回滾,邏輯比「只挑已提交的 redo」單純、且能正確處理交錯與巢狀情況。
問題 3:WAL 會不會讓寫入變兩倍(log 一次、資料一次)?
資料是寫了兩次沒錯(先 log 後資料頁),但 log 是循序、可合併(group commit),資料頁是延後、批次、可多次修改合併成一次寫回。整體 I/O 成本遠低於「每次 commit 都隨機刷所有資料頁」,對吞吐是淨賺。(吞吐面的詳細推導見 SQL Server 交易記錄檔管理 的循序寫小節。)
問題 4:WAL 和 undo log 為什麼都要?
因為主流採 steal + no-force:允許未提交髒頁被寫回(→ 要 undo 才能撤銷)、且 commit 不強制刷資料頁(→ 要 redo 才能補做)。少了任一種,就得放棄對應的緩衝策略、犧牲效能。
總結與速查
核心要點
- WAL 一句話:先寫日誌,再改資料;commit 只需 log 落地,資料頁延後批次刷。
- 兩條規則:Undo 規則(髒頁寫回前 log 先落地)+ Redo 規則(commit 前 log 先落地),分別支撐 steal 與 no-force。
- redo 補做已提交沒落地的;undo 撤銷未提交卻已落地的。
- 崩潰復原走 ARIES 三階段:Analysis → Redo(重演歷史)→ Undo(寫 CLR);靠 LSN / pageLSN 比對達成冪等。
- checkpoint 縮短 redo 起點;WAL 同時是複寫 / PITR / CDC 的資料來源。
快速記憶
| 方面 | 說明 |
|---|---|
| 是什麼 | 「先寫日誌再改資料」的持久化協定 |
| 為什麼 | 用便宜的循序 log 換掉昂貴的隨機資料頁寫,同時保證崩潰不丟/可回滾 |
| 怎麼復原 | Analysis → Redo → Undo(ARIES),用 LSN 保證冪等 |
| 副產品 | 複寫、時間點還原、CDC 全靠這條 log |
延伸
- SQL Server 的具體實作(記錄檔、復原模式、截斷/收縮、維運):見 SQL Server 交易記錄檔管理。
- WAL 在 ACID 中的定位(持久性→redo、原子性→undo):見 交易與 ACID 完全指南。
建立日期:2026-07-02