32.2 恢复目标与时间线
恢复目标由三部分组成:
where to stop time | xid | lsn | name | immediate | end-of-WAL
which history timeline
what to do there pause | promote | shutdown少写任何一项,工具都会替你选默认值;默认值可能技术上合理,却不一定符合这次事故。
特别是 recovery_target_inclusive 默认是 on,recovery_target_timeline 默认是
latest,recovery_target_action 默认是 pause。事故 runbook 应显式记录有效值,
不能把“没有写”当成“没有语义”。
32.2.1 时间、LSN、事务 ID 与命名恢复点
PostgreSQL 每次只接受一个停止目标
PostgreSQL 18 的目标设置如下:
| 目标 | 原生设置 | Pig 参数 | 语义与适用场景 |
|---|---|---|---|
| 最早一致点 | recovery_target='immediate' | -I | 在线 backup 完成的一致点 |
| 命名点 | recovery_target_name | --name | 事前调用 pg_create_restore_point() |
| 时间 | recovery_target_time | -t/--time | 有可靠 commit 时间与时区证据 |
| XID | recovery_target_xid | --xid | 已审计到目标事务提交身份 |
| LSN | recovery_target_lsn | --lsn | 已定位 WAL 边界 |
| WAL 末尾 | 不设置较早目标 | -d/--default | 尽可能恢复到 archive 末端 |
原生配置最多只能设置一个 immediate/name/time/xid/lsn 目标,否则启动报错
(Recovery Target Settings)。
end-of-WAL 是“重放所有可用 WAL”,不是“回到事故前”。
选择顺序可以写成:
reviewed named point exists?
yes -> name
no -> exact bad commit XID independently audited?
yes -> XID, then prove inclusive/exclusive
no -> exact WAL boundary independently derived?
yes -> LSN, then prove nearby transaction set
no -> bounded UTC commit interval?
yes -> time, restore multiple candidates
no -> continue evidence collectiontime:人最容易理解,也最容易被时钟骗
时间目标接受 timestamp with time zone 语义。生产票据应写完整偏移:
2026-07-30 03:09:12.483+00
2026-07-30 11:09:12.483+08不要只写:
03:09
yesterday 11:09
2026-07-30 11:09:12Pig 可以为不带时区的输入补当前本地时区,但自动补全是 CLI 解析行为,不是事故证据。 执行计划应把目标归一化成完整时间戳,再由操作者对照日志、server timezone 和原始来源 复核。
时间目标配合 recovery_target_inclusive:
inclusive=on 包含 commit timestamp 恰好等于目标的事务
inclusive=off 停在它之前真实系统中多个事务可能共享很接近的提交时间;日志时间还有格式精度和采集延迟。时间目标 更适合表达候选区间,而不是把一条聊天消息的分钟级时间强行当成精确提交点。
XID:本章实验的目标,但不是天然全序号
XID 在事务开始时顺序分配,事务可以按不同顺序结束:
XID 500 begins
XID 501 begins
XID 501 commits
XID 500 commits官方定义是:恢复那些在目标事务之前提交的事务,并根据 inclusive 决定是否包含目标
事务;不能用 xid < target 推断“更早提交”
(recovery_target_xid)。
本章实验故意串行制造三笔事务:
safe-before -> damage -> post-target所以 XID target 可无歧义地证明 inclusive/exclusive 差别。生产中若存在并发事务,仍要 从候选数据库查询业务 manifest,明确哪些并发提交被包含;XID 只是停止条件,不是业务 正确性的替代品。
不要依赖表行的 xmin 作为通用事故审计:
xmin是行版本创建者,不直接表达删除者或完整事务影响;- UPDATE 会产生新行版本;
- 一个事务可影响许多表,也可能回滚;
- XID 会 wraparound,显示和比较需要理解 epoch;
- freeze、复制与逻辑导出语义都可能让简单推断失效。
可控变更应在同一事务里记录 pg_current_xact_id() 与业务 change ID。
LSN:精确 WAL 位置,不自动等于业务提交
LSN 是 WAL 字节位置,适合:
- 从日志或工具精确得到 commit record 附近边界;
- 关联 archive segment 与 replay 进度;
- 在两个候选之间做技术二分;
- 证明 WAL 已覆盖到目标之后。
但 pg_current_wal_lsn() 是观察时的当前插入位置,不是自动返回“当前事务未来的 commit
record LSN”。在事务内部随手记一个 LSN,再把它命名为 commit LSN,会造成伪精确。
如需按 LSN 恢复,应说明 LSN 如何获得、对应哪条 WAL 记录,并用候选数据验证。
LSN 同样受 inclusive 控制。它可以告诉 PostgreSQL在何处停,却不能告诉业务“账户余额 是否正确”。
name:最清晰,但必须提前创建
SELECT pg_create_restore_point(
'before_price_rebuild_change_20260730'
);命名恢复点适合重大 DDL、批处理和发布窗口。名称应包含 change ID,创建结果与 LSN 要进 变更证据,并确认对应 WAL 已归档。它不是 bookmark 服务:只有已执行并写入 WAL 的恢复 点才能在未来被找到,也不能用新 backup 恢复到该 backup 结束之前的 named point。
recovery_target_inclusive 只适用于 time、XID 和 LSN,不用于 name。若想表达“危险事务
之前”,应在危险动作前创建 restore point,而不是事后猜 named point 的包含语义。
immediate 与 end-of-WAL
immediate 在基础备份达到一致状态时结束,通常用于验证基础备份本身;它不包含 backup
之后的 WAL 业务变化。end-of-WAL 会尽可能重放全部可用 WAL,适用于主机损失后追到
最新,而不适合排除已经归档的误操作。
目标必须晚于所选基础备份的结束点。若想恢复到 backup 进行期间的更早时刻,需要选择 更早一份基础备份;不是把 target 参数写得更早就能穿越该下界。
32.2.2 timeline 分叉与“恢复到刚刚之前”
PITR 会产生新历史
原历史:
timeline 11
A ---- B ---- damage ---- C ---- D恢复到 damage 之前并 promote:
timeline 11
A ---- B ---- damage ---- C ---- D
\
timeline 12
B' ---- E ---- F新 timeline 防止恢复后生成的 WAL 覆盖旧历史。PostgreSQL 会产生 timeline history 文件,记录从哪条历史、哪个位置分叉;它们会像 WAL 一样归档,且是多分支恢复选择所必需 的证据 (Timelines)。
因此 timeline ID 不是“恢复次数”或“谁更新”,而是 WAL 历史分支身份。看到 timeline 变大也不能单独断言一次未授权 failover;要读 history、DCS、日志和变更记录。
current 与 latest 的参照点
PostgreSQL 的定义非常具体:
current 沿基础备份创建时所在的 timeline 恢复
latest 沿 archive 中可找到的最新 timeline 恢复;默认值
N 沿明确的十进制 timeline
0xN 沿明确的十六进制 timelinelatest 对持续追随新 primary 的 standby 很有用,但在多次事故演练后,archive 里可能
已有后来创建的实验分支。若本次目标是原事故历史,盲用 latest 可能沿错分支。本章正式
实验显式使用 current,把恢复限定在 fresh backup 所属的 timeline 11;候选 promote
后各自在隔离目录进入新 timeline,但 archive_mode=off,不把实验分支推回 source
repository。
复杂 re-recovery 必须画出 lineage:
backup label
backup start / stop LSN
backup timeline
target timeline
parent timeline and switchpoint
required history files
required WAL min/max
why latest/current/explicit is chosen“仓库里有这个 WAL 文件名”仍不够。WAL 文件名前八位是 timeline 的十六进制表示;同一 逻辑 segment 位置在不同 timeline 上可属于不同历史。
“刚刚之前”是 exclusive 语义
time、XID、LSN 的 recovery_target_inclusive 默认是 on:
on stop just after target
off stop just before target如果目标 XID 正是误更新事务:
| 候选 | damage | 结论 |
|---|---|---|
| inclusive / 默认 | 存在 | 应否决 |
exclusive / -X | 不存在 | 才可能接受 |
这不是文案差别,而是一整笔事务是否存在。本章不只检查配置里写了 exclusive,还在
恢复候选中查询 damage audit、错误状态和 outbox 行,证明错误确实不存在。
target action 决定到点后的生命周期
| action | 到点行为 | 适用 | 注意 |
|---|---|---|---|
pause | 暂停 WAL replay | 希望只读检查目标 | 默认;需判断是否真的 paused |
promote | 结束 recovery,开始接受连接 | 隔离候选需完整验证或导出 | 会创建新 timeline |
shutdown | 到点后停库 | 希望保留精确停点目录 | recovery.signal 仍在,重启前要处理配置 |
官方还规定:设置了目标,但 archive recovery 在到达它之前已经结束,server 会 fatal shutdown。不要把这种失败解释成“恢复到了最接近的位置”;目标没有到达就是阻断。
本章使用 promote,但只在 private Unix socket 上启动。promote 的“接受连接”不等于
接受业务流量,也不等于已重新接入 Patroni。
用两个候选替代一场争论
当团队争论目标是否应 inclusive 时,最稳妥的做法通常不是在事故群里投票,而是:
same backup
same target
same source timeline
same validation probes
candidate A inclusive
candidate B exclusive然后让数据回答。候选差异应聚焦目标事务;如果两者还有其他意外差异,说明 backup、 timeline、并发提交或验证合同仍未被理解。
32.2.3 时钟、时区和证据误差
PostgreSQL 有不止一个“现在”
在一笔长事务中:
SELECT
current_timestamp,
statement_timestamp(),
clock_timestamp();current_timestamp/transaction_timestamp()固定为事务开始时刻;statement_timestamp()是当前语句开始时刻;clock_timestamp()返回实际墙钟,会在语句执行中变化。
若审计列默认使用 current_timestamp,长事务提交时记录的值可能接近事务开始,不是
commit 时刻。日志行时间、应用 request time、消息 broker time 又分别来自不同进程。
“按 audit.created_at 恢复”之前必须先确认这个列的时间语义。
建立时钟证据
恢复票据至少记录:
PostgreSQL TimeZone and log_timezone
database host UTC time and synchronization state
application host UTC time
collector/queue timestamp semantics
client-provided timestamps and trust level
log timestamp precision
known NTP offset or uncertainty
conversion rule used by CLI使用完整 UTC 或 numeric offset,不使用歧义缩写。官方建议 recovery time 使用数值偏移
或完整 zone name;缩写是否可用取决于更早加载的 timezone_abbreviations。
把误差写进搜索区间
设:
- $t_o$ 是观察到的时间;
- $\epsilon_c$ 是时钟偏差;
- $\epsilon_l$ 是日志/采集延迟;
- $\epsilon_s$ 是时间戳精度与语义误差。
则候选区间至少为:
$$ [t_o-(\epsilon_c+\epsilon_l+\epsilon_s), t_o+(\epsilon_c+\epsilon_l+\epsilon_s)] $$
这不是统计置信区间,而是操作上的保守边界。区间越宽,越应恢复多个候选或改用独立 XID/name/LSN 证据。
夏令时与本地日期是事故放大器
2026-11-01 01:30:00 在采用夏令时回拨的地区可能出现两次;2026-07-30 作为 Pig
time 输入会补为本地当天 00:00:00;只写 12:00:00 还会补“今天”。这些便捷格式
适合交互,不适合冻结事故目标。
生产票据应保存:
raw source timestamp
source timezone
normalized UTC timestamp
normalization tool/version
operator-reviewed target
inclusive/exclusive
candidate validation result目标选择验收表
| 检查 | 通过证据 | 失败动作 |
|---|---|---|
| 一个目标 | effective config / Pig plan 只有一种 target | 修正冲突参数 |
| target 可达 | backup end < target 且 archive 覆盖 | 选更早 backup 或修复 WAL |
| timeline 正确 | history + backup lineage + explicit rationale | 不启动 |
| inclusive 明确 | 两候选或业务边界证明 | 默认恢复两次 |
| action 明确 | pause/promote/shutdown 与后续步骤一致 | 修正生命周期 |
| 时区明确 | raw + UTC + offset + clock evidence | 扩大候选区间 |
| 业务探针明确 | damage/safe/good-after 预期 | 回第 32.1 节 |
恢复计划至此才具有可执行语义。下一节把它放进一个不会覆盖现场、不会接入真实服务、 不会发送外部消息的隔离环境。
上一节:先界定误操作 · 返回本章目录 · 下一节:隔离恢复策略 · 查看全书目录 · 查看索引中心