如何设计 WAL 和崩溃恢复
WAL 解决持久解释问题:崩溃后保留已提交,撤销未提交。
边界:事务运行时的锁、隔离和普通回滚在
事务系统。这一页只讲断电、kill、重启后的恢复证据。
| 失败 | 原因 | 需要什么 |
| commit 返回后 kill,重启看不到修改。 | 数据页还在 Buffer Pool,没落盘。 | Redo。 |
| 未提交事务的脏页被刷盘。 | Buffer Pool 为了腾空间刷了 dirty page。 | Undo。 |
| 表页和索引页只落了一半。 | 跨页修改没有持久证据。 | WAL 记录修改顺序。 |
1. append UPDATE log
2. change page in Buffer Pool
3. append COMMIT log
4. flush WAL through commit LSN
5. return success
| 规则 | 为什么 |
| 数据页刷盘前,对应日志必须先刷盘。 | 否则崩溃后没有证据解释这个页。 |
| commit 返回前,commit log 必须刷盘。 | 否则用户看到成功,数据库却没有持久承诺。 |
| 每个页记录 pageLSN。 | 恢复时判断这个页是否已经包含某条日志。 |
wal_record:
lsn
tx_id
type -- BEGIN / UPDATE / COMMIT / ABORT / CLR
page_id
before_image
after_image
prev_lsn
| 阶段 | 回答什么问题 |
| Analysis | 崩溃时哪些事务已提交,哪些事务没提交。 |
| Redo | 已提交修改是否有些还没落到数据页。 |
| Undo | 未提交修改是否已经污染了数据页。 |
winners = tx with COMMIT
losers = tx with BEGIN but no COMMIT
redo needed winner updates
undo losers in reverse LSN order
| 没有 checkpoint | 有 checkpoint |
| 每次恢复都从第一条日志扫起。 | 从最近恢复点附近开始。 |
| 数据库运行越久,恢复越慢。 | 恢复时间和最近日志量相关。 |
| 无法快速知道脏页和活跃事务。 | checkpoint 保存 dirty page table 和 tx table。 |
| 实验 | 通过标准 |
| commit 前 kill。 | 重启后修改被撤销。 |
| commit 后、数据页刷盘前 kill。 | 重启后修改被 Redo。 |
| 未提交脏页强制刷盘后 kill。 | 重启后修改被 Undo。 |
| 表页落盘、索引页未落盘。 | 恢复后访问路径一致。 |
| 大量日志后 checkpoint。 | 恢复不从历史第一条日志开始。 |
一句话:WAL 不是“写日志”四个字,而是一套顺序承诺:先有日志证据,再让数据页落盘;先有 commit 证据,再告诉用户成功。