“与 WAL 相对,影子分页恢复通过写时复制和根指针切换保留旧页,不以逐更新日志为主要恢复依据。两者都要证明稳定写入顺序,不能把“无 WAL”误写成“无需持久化协议”。”
形式陈述 ​
影子分页为逻辑页号维护到物理页的映射。开始更新型 数据库事务 时,稳定的 shadow page table
提交遵循持久化顺序:先稳定所有新数据页,再稳定当前页表及其间接页,最后以原子根指针切换发布
根切换的“原子”必须由具体介质实现,例如单个原子扇区、带代数编号与校验的双根槽,或更底层的持久事务。若数据页尚未稳定就发布根,新映射会指向缺失内容;若旧页过早回收,切换前崩溃也无法退回。
大型页表通常本身是树。更新叶映射时只复制从根到该叶的路径,未改变的子树仍与影子版本共享;提交发布新根后,旧根及共享节点保持自洽。这个结构降低整表复制,却把引用计数或可达性回收变成必要元数据。
直觉
这种恢复方式不像在原稿上逐笔修改并记日记,而是保留上一版目录,另做一套改页。所有新页准备好后,只把书封内的目录指针翻到新版。崩溃若发生在翻页前,读旧版;翻页后,读新版。
真正昂贵的不是一个指针,而是目录复制、空洞回收和并发版本管理。页表可以用树形结构按路径写时复制,避免复制整个映射,却仍要维护哪些旧页可安全回收。
旧根是一份完整提交快照,新根是一份候选快照。只要两者引用的不可变页都保留,恢复无需逐条反演更新;代价是每次小改动也可能分配数据页和若干目录页,空间局部性会随时间破碎。
例子与边界
稳定根
A -> page 10
B -> page 20
事务只修改 B。系统分配 page 31,写入 B 的新内容,构造当前映射
若直接覆盖 page 20,再更新页表,就已经丢失影子副本;若先切根再刷 page 31,新根可能发布一页未持久化内容。两个顺序错误分别破坏 abort 路径与 commit 持久性。
多个并发写事务不能无条件各自覆盖同一个根。系统需要串行根提交、版本验证或更复杂的合并协议;长生命周期旧根还会延迟空间回收。随机写造成的碎片、页表路径复制和垃圾收集,是影子分页在大型高并发数据库中的主要边界。
自由空间表也处于恢复边界。若新页已标记占用但事务 abort,应安全回收;若根已发布却把新页误列为空闲,后续分配会覆盖提交数据。把 allocator 元数据排除在影子映射外,需要另一套等强的原子更新协议。
根切换只保证单个影子分页域内的提交。若一次事务跨两个文件、两个设备或一个数据库与外部队列,分别切根可能在崩溃后得到一新一旧;仍需更高层原子提交,不能把单根原子性直接乘到多个资源。
推论与应用
与 预写日志 相比,影子分页把主要恢复证据放在不可变旧页和根切换上,而 WAL 允许原地页更新并用日志 undo/redo。二者都依赖精确的持久化屏障;“不写更新日志”不意味着可以忽略写入顺序、校验与介质故障。
影子根天然支持快照和快速 abort,但旧页回收需要可达性或引用协议。备份、介质损坏和跨文件原子性仍需额外设计。若实现又为增量复制、并发控制或媒体恢复加入日志,它就不再是纯粹的二选一架构,评估时应逐层列出各自职责。
故障测试至少覆盖新数据页写入中、页表路径写入中、根槽一半更新、根发布后旧页回收前后。恢复只能选择校验通过且依赖页已稳定的一代;按“时间戳较新”盲选根,可能把撕裂的新版本当成提交状态。
参考资料
- Raymond A. Lorie, “Physical Integrity in a Large Segmented Database,” ACM Transactions on Database Systems 2(1), 1977, pp. 91–104。
- Philip A. Bernstein, Vassos Hadzilacos, and Nathan Goodman, Concurrency Control and Recovery in Database Systems, Addison-Wesley, 1987, Ch. 6。
- Jim Gray and Andreas Reuter, Transaction Processing: Concepts and Techniques, Morgan Kaufmann, 1992, recovery chapters。