# 恢复目标与时间线

LLMS 索引： [llms.txt](/llms.txt)

---

恢复目标由三部分组成：

```text
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 与命名恢复点 {#item-32-2-1}

### 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](https://www.postgresql.org/docs/18/runtime-config-wal.html#RUNTIME-CONFIG-WAL-RECOVERY-TARGET)）。
`end-of-WAL` 是“重放所有可用 WAL”，不是“回到事故前”。

选择顺序可以写成：

```text
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 collection
```

### time：人最容易理解，也最容易被时钟骗

时间目标接受 `timestamp with time zone` 语义。生产票据应写完整偏移：

```text
2026-07-30 03:09:12.483+00
2026-07-30 11:09:12.483+08
```

不要只写：

```text
03:09
yesterday 11:09
2026-07-30 11:09:12
```

Pig 可以为不带时区的输入补当前本地时区，但自动补全是 CLI 解析行为，不是事故证据。
执行计划应把目标归一化成完整时间戳，再由操作者对照日志、server timezone 和原始来源
复核。

时间目标配合 `recovery_target_inclusive`：

```text
inclusive=on   包含 commit timestamp 恰好等于目标的事务
inclusive=off  停在它之前
```

真实系统中多个事务可能共享很接近的提交时间；日志时间还有格式精度和采集延迟。时间目标
更适合表达候选区间，而不是把一条聊天消息的分钟级时间强行当成精确提交点。

### XID：本章实验的目标，但不是天然全序号

XID 在事务开始时顺序分配，事务可以按不同顺序结束：

```text
XID 500 begins
XID 501 begins
XID 501 commits
XID 500 commits
```

官方定义是：恢复那些在目标事务之前提交的事务，并根据 inclusive 决定是否包含目标
事务；不能用 `xid < target` 推断“更早提交”
（[recovery_target_xid](https://www.postgresql.org/docs/18/runtime-config-wal.html#GUC-RECOVERY-TARGET-XID)）。

本章实验故意串行制造三笔事务：

```text
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：最清晰，但必须提前创建

```sql
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 分叉与“恢复到刚刚之前” {#item-32-2-2}

### PITR 会产生新历史

原历史：

```text
timeline 11
  A ---- B ---- damage ---- C ---- D
```

恢复到 damage 之前并 promote：

```text
timeline 11
  A ---- B ---- damage ---- C ---- D
          \
timeline 12
            B' ---- E ---- F
```

新 timeline 防止恢复后生成的 WAL 覆盖旧历史。PostgreSQL 会产生 timeline history
文件，记录从哪条历史、哪个位置分叉；它们会像 WAL 一样归档，且是多分支恢复选择所必需
的证据
（[Timelines](https://www.postgresql.org/docs/18/continuous-archiving.html#BACKUP-TIMELINES)）。

因此 timeline ID 不是“恢复次数”或“谁更新”，而是 WAL 历史分支身份。看到 timeline
变大也不能单独断言一次未授权 failover；要读 history、DCS、日志和变更记录。

### `current` 与 `latest` 的参照点

PostgreSQL 的定义非常具体：

```text
current  沿基础备份创建时所在的 timeline 恢复
latest   沿 archive 中可找到的最新 timeline 恢复；默认值
N        沿明确的十进制 timeline
0xN      沿明确的十六进制 timeline
```

`latest` 对持续追随新 primary 的 standby 很有用，但在多次事故演练后，archive 里可能
已有后来创建的实验分支。若本次目标是原事故历史，盲用 `latest` 可能沿错分支。本章正式
实验显式使用 `current`，把恢复限定在 fresh backup 所属的 timeline 11；候选 promote
后各自在隔离目录进入新 timeline，但 `archive_mode=off`，不把实验分支推回 source
repository。

复杂 re-recovery 必须画出 lineage：

```text
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`：

```text
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 时，最稳妥的做法通常不是在事故群里投票，而是：

```text
same backup
same target
same source timeline
same validation probes
candidate A inclusive
candidate B exclusive
```

然后让数据回答。候选差异应聚焦目标事务；如果两者还有其他意外差异，说明 backup、
timeline、并发提交或验证合同仍未被理解。

## 32.2.3 时钟、时区和证据误差 {#item-32-2-3}

### PostgreSQL 有不止一个“现在”

在一笔长事务中：

```sql
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 恢复”之前必须先确认这个列的时间语义。

### 建立时钟证据

恢复票据至少记录：

```text
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` 还会补“今天”。这些便捷格式
适合交互，不适合冻结事故目标。

生产票据应保存：

```text
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 节 |

恢复计划至此才具有可执行语义。下一节把它放进一个不会覆盖现场、不会接入真实服务、
不会发送外部消息的隔离环境。

---

[上一节：先界定误操作](../01/) · [返回本章目录](../) · [下一节：隔离恢复策略](../03/) ·
[查看全书目录](/toc/) · [查看索引中心](/indexes/)
