C06 · 第 6 章 · 事务与恢复
数据库日志与恢复
说明缓冲页、预写日志和提交顺序,计算重做恢复结果,并区分检查点、日志归档、备份与时间点恢复。
本章目标
- 检查日志持久化与页面写出之间的顺序。
- 在简化恢复模型中判断提交事务的最终值。
- 解释重复恢复所需的幂等性条件。
- 估算恢复读取下限并说明备份验证范围。
本页目录
缓冲页与崩溃状态
数据库修改一条记录时,通常先更新内存中的缓冲页。内存页已经改变而磁盘页尚未更新时,称它为脏页。若系统突然停止,内存中的修改和尚未持久化的日志都可能丢失,磁盘上则可能只保存某些已经写出的页面。恢复过程必须根据持久化信息重新确定哪些事务成立以及页面应处于什么状态。
事务提交时强制写出所有修改页,能够简化部分恢复工作,但一次事务可能涉及许多分散页面,逐页等待会增加开销。允许数据页稍后写出,就需要在其他持久化记录中保存恢复依据。日志按顺序记录修改及提交信息,使系统能够在重新启动时补做必要操作。
恢复协议与缓冲区策略相互关联。若允许未提交事务的脏页提前写入存储,恢复可能需要处理这些未完成修改;若提交时允许部分修改页仍在内存,就需要重做已提交的修改。经典恢复算法会根据这些条件设计撤销和重做步骤。具体数据库可能结合多版本结构、页面日志和可见性信息采用不同实现。
预写日志与持久化顺序
预写日志通常简称 WAL。其核心顺序约束是:把某次修改后的数据页写入永久存储前,描述该修改的必要日志必须先持久化。这样即使页面已经改变而系统中断,恢复过程仍有依据解释和处理页面状态。同步提交还要求在向客户端确认成功前,把相应提交记录及所依赖的日志写入约定的持久存储。
设日志序列号为 ,它标记日志中的位置;页上保存最后一次相关修改的序列位置,记作 ;已经持久化的日志末端记作 。写出该数据页所需的顺序条件为
这个条件描述日志与数据页的持久化顺序。日志还需足够描述恢复动作,并处理不完整写入、校验和与存储错误。数据库依赖操作系统和设备对持久化请求的实现;如果设备错误报告已落盘,单靠软件顺序无法提供预期保证。
某页面最新修改的序列号为 230,当前已持久化日志末端为 210。事务提交记录位于 260。判断页面何时允许写出,以及同步提交何时允许确认成功。
当前 ,日志尚未覆盖页面修改,不能先写出该页面。把日志持久化到 240 后,,页面写出满足预写要求。不过提交记录 260 仍未持久化,向客户端确认同步提交的条件尚未满足。日志进一步持久化到 260 后,才能在本地持久化承诺下确认该次提交。
此时数据页仍可能留在内存,恢复时可以通过日志重做。若系统还承诺同步副本持久化,需要另外等待相应副本确认;本例只讨论本地日志与数据页的关系。页面写出资格、事务提交资格和跨节点确认应分别检查。
PostgreSQL 17 的 WAL 介绍说明日志先于数据页持久化,以及崩溃后利用日志补做页面修改的关系。异步提交允许更早返回成功,在崩溃时可能丢失最近尚未持久化的事务;应用选择该模式前应明确允许的数据损失范围。
重做、撤销与幂等操作
重做把日志中已经确定需要保留的修改重新应用到页面。撤销清除不应保留的事务效果。两者具体采用什么日志、按照什么顺序执行,取决于恢复算法。教学中常用包含旧值与新值的日志说明两种操作;生产实现可能记录页面级变化、逻辑操作或其他结构。
恢复过程自身也可能中断,因此重复执行恢复动作必须保持正确。若日志记录目标值为十五,再次把该字段设为十五不会重复累加;若日志只写加五,而恢复无法判断是否已执行,重复恢复就可能把十五变成二十。页面序列号、补偿记录等机制用于判断已完成步骤或保证重复操作的效果。
采用一个简化模型:未提交事务的修改只保存在私有内存中,提交记录持久化后才允许把修改写到数据页,日志记录每项修改的新值。初始磁盘值为 。事务 把它们改成 并完成日志提交;之后只有页面 写回磁盘。事务 在私有内存中把 改成 99,尚未提交时系统崩溃。
重新启动时,磁盘为 。日志表明 已提交,因此重做它的两项赋值,得到 。 没有提交记录,且题设禁止它的修改提前进入数据页,所以放弃其私有修改即可。最终保留的状态与只执行 一致。
再执行一次同样的重做,赋值仍得到十五和二十五,结果保持不变。这个例子依靠禁止未提交页面写出的假设,所以没有展示撤销;允许此类页面提前写出的算法必须增加相应处理。该模型用于分析顺序和结果,不代表 PostgreSQL 的完整内部实现。
PostgreSQL 使用多版本行状态和事务提交信息判断可见性,崩溃恢复主要沿 WAL 重放物理修改。未提交事务所留下的版本依照可见性规则不对普通查询生效,后续再由清理机制回收。把经典教材中的逐条旧值撤销流程直接当成该产品的实现,会混淆两种不同机制。
检查点与日志保留
从系统创建以来重放所有日志会越来越慢。检查点记录一组恢复进度信息,使恢复能够从较近的位置开始,同时安排必要的脏页写出。简单静止系统可以暂停更新并形成明确的一致点;繁忙系统常采用与事务并发的检查点过程,需要保存足够的信息描述期间仍在变化的状态。
检查点完成不等于所有旧日志都能立即删除。备份、复制、活动事务以及其他恢复需求可能仍依赖较早日志。保留范围必须由系统的恢复协议和使用者共同决定。手工删除看似过旧的日志,可能使最近备份无法恢复,或让落后的副本无法追赶。
检查点频率也存在代价。过于频繁会增加写回工作和日志开销,间隔过长则可能增加恢复需要重放的日志量。调整时应同时观察正常负载延迟、日志生成速率和恢复耗时。仅降低某一次启动时间,可能把大量写入压力转移到正常服务期间。
备份与时间点恢复
崩溃恢复依赖原数据文件和必要日志仍然可读;设备损坏或文件被误删时,需要独立备份。物理备份保存数据库文件状态,日志归档补充备份之后的修改。恢复到特定时间点时,先恢复基准备份,再按协议重放连续日志直到目标位置,并检查事务提交边界。
备份文件存在只能证明保存了一份数据,恢复演练才检验它能否满足使用要求。演练应检查备份完整性、日志连续性、软件版本兼容、密钥或配置依赖,以及恢复后的应用约束。目标恢复时间包括拷贝、日志重放、检查和启动各阶段;目标数据损失窗口则取决于备份与日志的持久化范围。
假设恢复需要读取两 GiB 基准备份和五百一十二 MiB 日志,持续读取速度为每秒六十四 MiB。这里一 GiB 等于一千零二十四 MiB,总读取量为二千五百六十 MiB,单纯读取下限为四十秒。实际恢复还包含日志计算、随机页面写入和一致性检查,因此总时间通常更长,不能仅用网络带宽预测恢复完成时间。
批量提交能让多个事务共用一次日志同步。若一次同步耗时五毫秒,每批包含二十个事务,忽略其他开销时每秒最多完成约四千个事务的同步确认。该计算只说明同步次数如何影响容量;等待成批会增加部分请求的延迟,存储吞吐和处理器也可能先成为限制。
恢复条件练习
页面序列号为 320,已持久化日志末端分别为 310 和 350。判断两种情况下是否允许写出该页,并说明它与提交确认的区别。
查看解答
日志持久化末端必须至少达到三百二十,数据页才满足写出条件。达到三百一十仍不足;达到三百五十足够,但提交确认还要另看提交记录的位置。
字段初始值五,比较把值设为十二与把值加七两种恢复指令各执行两次的结果。说明恢复中断后重启时应解决的问题。
查看解答
重复执行设值操作后仍为十二。重复执行加七会由五变成十二再变成十九。恢复需要识别已经执行的日志记录,或采用具备相应幂等保证的处理方式。
基准备份为两 GiB,日志为一 GiB,读取速度每秒一百二十八 MiB。计算读取时间下限,并列出总恢复时间的其他组成。
查看解答
共读取三千零七十二 MiB,按每秒一百二十八 MiB 计算需要二十四秒。这只是持续读取时间,未包含日志应用、写回、校验和服务启动。
客户端收到异步提交成功后,机器立即断电。解释为什么最近事务可能丢失,以及应用应如何选择提交保证。
查看解答
异步确认时提交日志可能仍在内存,随后崩溃会丢失这些最近事务。恢复应回到日志已持久化的一致状态;依赖成功确认的外部操作需要采用符合其持久性要求的提交配置。
日志与备份参考资料
PostgreSQL 17 Documentation: Write-Ahead Logging (WAL)
PostgreSQL Global Development Group · PostgreSQL Global Development Group
打开官方来源PostgreSQL 17 的 WAL 文档说明日志先行写入与重做恢复,适合核对页面写回和提交确认之间的顺序关系。
PostgreSQL 17 Documentation: Asynchronous Commit
PostgreSQL Global Development Group · PostgreSQL Global Development Group
打开官方来源异步提交章节描述返回成功与日志持久化之间的时间窗口,以及崩溃时最近事务可能丢失的条件。
PostgreSQL 17 Documentation: Continuous Archiving and Point-in-Time Recovery (PITR)
PostgreSQL Global Development Group · PostgreSQL Global Development Group
打开官方来源连续归档与时间点恢复章节说明基准备份、日志归档和恢复目标的配合方式。恢复演练应根据实际部署版本完成,不能只凭日志数量推断备份可用性。