时间点恢复(PITR)是PostgreSQL中一个强大的功能,允许将数据库还原到任何特定的时间点,而不仅仅是还原到上次完整备份的状态。时间点恢复将基础备份与预写日志(WAL)文件的连续归档相结合,使PostgreSQL数据库能够恢复到自基础备份以来的任何时间点。
一、PITR 的含义
1.1一句话定义
PITR = Point-In-Time Recovery(时间点恢复)
是一种数据库灾难恢复技术,通过 "基础备份 + 连续归档日志重放",将数据库精确恢复到过去任意指定时间点的状态。
1.2拆解这个缩写
| 英文 | 中文 | 含义 |
|---|---|---|
| Point | 时间点 | 精确到秒/事务的某一时刻 |
| In | 在 | 在这个时刻 |
| Time | 时间 | 不是恢复到"备份那天",而是任意时刻 |
| Recovery | 恢复 | 把数据回滚/重放到那个状态 |
1.3核心原理
html
┌────────────────────────────────────────────────────┐
│ ① 基础备份(Base Backup) │
│ → 某个时刻的全库物理快照(如凌晨2:00) │
│ │
│ ② WAL/归档日志持续记录(2:00 → 故障时刻) │
│ → 每条数据修改都先写日志,再写数据页 │
│ → 日志实时归档到独立存储 │
│ │
│ ③ 重放日志到目标时间点 → 停止 → 恢复完成 │
│ → 比如停在 9:59:59(误删之前1秒) │
└────────────────────────────────────────────────────┘
本质:不是"恢复到备份那天",而是"从备份那天开始,把之后所有操作重放一遍,在你指定的时刻停下"。
1.4三种恢复目标
| 参数 | 示例 | 说明 |
|---|---|---|
| recovery_target_time | '2026-10-02 09:59:59' | 恢复到指定时间点(最常用) |
| recovery_target_xid | '123456' | 恢复到指定事务ID之前 |
| recovery_target_lsn | '0/4003C00' | 恢复到指定WAL位置 |
| recovery_target_name | 'before_upgrade' | 恢复到预定义的还原点 |
1.5举个例子
html
2:00 ──── 全库物理备份(pg_basebackup)
2:00~10:00 ── WAL日志持续归档(每5分钟切换一个文件)
10:00 ──── ❌ 运维误删 orders 表
10:01 ──── 决定恢复到 9:59:59
恢复过程:
还原2:00的全备 → 从归档拉取2:00~9:59:59的WAL → 逐条重放 → 停在9:59:59
结果:orders表数据完好,丢失几乎为0(RPO≈1秒)
1.6 PITR vs 其他恢复方式对比
| 方式 | 恢复粒度 | 恢复精度 | 速度 | 适用场景 |
|---|---|---|---|---|
| PITR | 整个集群 | 精确到秒/事务 | 较慢 | 误删/灾难/数据损坏 |
| 逻辑备份 pg_dump | 单表/单库 | 备份时刻快照 | 快 | 数据迁移/单表恢复 |
| 崩溃恢复(自动) | 整个集群 | 最新一致点 | 极快 | 宕机自动恢复 |
注意
**PostgreSQL 原生 PITR 是集群级(cluster-level)恢复,无法直接单表恢复。**
想单表恢复?需要 PITR 全库恢复后,再用 pg_dump -t 表名 提取单表回灌。
一句话理解
PITR = 数据库的"时光机":全备打底 + 日志回放 + 精确刹车,让你回到过去任意一秒。
二、场景说明
每天2:00备份,第二天10:00误删除数据库,如何恢复?
2.1故障恢复过程
备份数据和归档
还原流程
- 还原完全备份
- 归档日志恢复:
- 备份中的归档
- 恢复2:00到10:00之间的归档
- 恢复在线redo
2.2环境准备
准备两台机器
| 数据库 | IP | 描述 |
|---|---|---|
| PostgreSQL 17.11 | 10.0.0.18 | pgserver,PG服务器,主库 |
| PostgreSQL 17.11 | 10.0.0.28 | backup,备份服务器 |
2.3备份
bash
#在PG服务器开启归档
[root@pgserver ~]#vim /apps/pgsql/data/postgresql.conf
archive_mode = on
archive_command = 'test ! -f /apps/pgsql/archive/%f && cp %p /apps/pgsql/archive/%f'
[root@pgserver ~]#su - postgres
[postgres@pgserver ~]$pg_ctl restart -D /apps/pgsql/data/
#在PG服务器上创建测试数据
postgres=# CREATE database testdb;
postgres=# \c testdb
testdb=# CREATE table t1(id int);
testdb=# INSERT into t1 values(1);
testdb=# select * from t1;
id
----
1
testdb=# SHOW listen_addresses;
listen_addresses
------------------
127.0.0.1
#修改配置文件
[postgres@pgserver ~]$vim /apps/pgsql/data/postgresql.conf
listen_addresses = '*'
#重启主库
[postgres@pgserver ~]$pg_ctl restart -D /apps/pgsql/data/
testdb=# SHOW listen_addresses;
listen_addresses
------------------
*
#确认端口在监听
[postgres@pgserver ~]$ss -tlnp | grep 5432
LISTEN 0 100 0.0.0.0:5432 0.0.0.0:* users:(("postgres",pid=1861,fd=7))
LISTEN 0 100 [::]:5432 [::]:* users:(("postgres",pid=1861,fd=8))
[postgres@pgserver ~]$cat /apps/pgsql/data/pg_hba.conf | grep -v "^#" | grep -v "^$"
local all all md5
host all all 127.0.0.1/32 md5
host all all ::1/128 md5
local replication all md5
host replication all 127.0.0.1/32 md5
host replication all ::1/128 md5
host replication postgres 10.0.0.28/32 md5
#Reload 配置
[postgres@pgserver ~]$pg_ctl reload -D /apps/pgsql/data/
[postgres@pgserver ~]$firewall-cmd --list-ports
FirewallD is not running
[postgres@pgserver ~]$ll /apps/pgsql/data/postgresql.conf.bak.20260916
-rw------- 1 root root 31029 Sep 16 12:17 /apps/pgsql/data/postgresql.conf.bak.20260916
[postgres@pgserver ~]$rm -f /apps/pgsql/data/postgresql.conf.bak.20260916
#在备份服务器对PG数据库进行远程完全备份
[root@backup ~]#su - postgres
[postgres@backup ~]$pg_basebackup -D /apps/pgsql/backup/ -Ft -Pv -U postgres -h 10.0.0.18 -p 5432 -R
Password:
pg_basebackup: initiating base backup, waiting for checkpoint to complete
pg_basebackup: checkpoint completed
pg_basebackup: write-ahead log start point: 0/28000028 on timeline 1
pg_basebackup: starting background WAL receiver
pg_basebackup: created temporary replication slot "pg_basebackup_1973"
WARNING: aborting backup due to backend exiting before pg_backup_stop was called
0/78992 kB (100%), 1/1 tablespace
pg_basebackup: error: backup failed: ERROR: could not open file "./postgresql.conf.bak.20260916": Permission denied
pg_basebackup: removing contents of data directory "/apps/pgsql/backup/"
#彻底清理
# 1. 退出 postgres,切回 root
[postgres@backup ~]$exit
# 2. 彻底清空 backup 目录
[root@backup ~]#rm -rf /apps/pgsql/backup/*
# 3. 重建空目录,权限确保正确
[root@backup ~]#mkdir -p /apps/pgsql/backup
[root@backup ~]#chown postgres:postgres /apps/pgsql/backup
[root@backup ~]#chmod 700 /apps/pgsql/backup
# 4. 确认目录干净
[root@backup ~]#ls -la /apps/pgsql/backup/
[postgres@backup ~]$find /apps/pgsql/data/ -name "postgresql.conf.bak*"
/apps/pgsql/data/postgresql.conf.bak.20260916
#现在执行备份
[root@backup ~]#su - postgres
[postgres@backup ~]$pg_basebackup -D /apps/pgsql/backup/ -Ft -Pv -U postgres -h 10.0.0.18 -p 5432 -R
[postgres@backup ~]$pg_basebackup -D /apps/pgsql/backup/ -Ft -Pv -U postgres -h 10.0.0.18 -p 5432 -R
Password:
pg_basebackup: initiating base backup, waiting for checkpoint to complete
pg_basebackup: checkpoint completed
pg_basebackup: write-ahead log start point: 0/2C000028 on timeline 1
pg_basebackup: starting background WAL receiver
pg_basebackup: created temporary replication slot "pg_basebackup_2053"
78972/78972 kB (100%), 1/1 tablespace
pg_basebackup: write-ahead log end point: 0/2C000120
pg_basebackup: waiting for background process to finish streaming ...
pg_basebackup: syncing data to disk ...
pg_basebackup: renaming backup_manifest.tmp to backup_manifest
pg_basebackup: base backup completed
#验证备份
# 看备份目录结构
[postgres@backup ~]$ls -la /apps/pgsql/backup/
total 95552
drwx------ 2 postgres postgres 63 Oct 2 22:37 .
drwxr-xr-x 10 postgres postgres 108 Aug 18 17:25 ..
-rw------- 1 postgres postgres 189784 Oct 2 22:37 backup_manifest
-rw------- 1 postgres postgres 80868864 Oct 2 22:37 base.tar
-rw------- 1 postgres postgres 16778752 Oct 2 22:37 pg_wal.tar
#验证 tar 里面有没有恢复配置
[postgres@backup ~]$tar -tf /apps/pgsql/backup/base.tar | grep -E "standby|postgresql"
postgresql.auto.conf
postgresql.conf
standby.signal
#在PG服务器上继续生成测试数据
testdb=# INSERT into t1 values(2);
#模拟数据库删除
testdb=# \c postgres
postgres=# DROP DATABASE testdb;
#发现故障,停止用户访问
#查看当前日志文件
postgres=# select pg_walfile_name(pg_current_wal_lsn());
pg_walfile_name
--------------------------
00000001000000000000002D
#查看当前事务ID
postgres=# select txid_current();
txid_current
--------------
839
2.4主库故障还原
bash
#在PG服务器上切换归档日志
postgres=# select pg_switch_wal();
pg_switch_wal
---------------
0/2D0006F0
#在要还原的服务器停止服务,准备还原
[postgres@pgserver ~]$pg_ctl stop -D /apps/pgsql/data/
[postgres@pgserver ~]$rm -rf /apps/pgsql/data/*
[postgres@pgserver ~]$chown -R postgres:postgres /apps/pgsql/data/
[postgres@pgserver ~]$chmod 700 /apps/pgsql/data/
#复制PG服务器的归档日志到还原的测试服务器
[postgres@pgserver ~]$rsync -a 10.0.0.28:/apps/pgsql/archive/ /apps/pgsql/archive/
postgres=# \c testdb
testdb=# SELECT * FROM t1;
id
----
1
3
[postgres@pgserver ~]$pg_controldata
pg_control version number: 1700
Catalog version number: 202406281
Database system identifier: 7675300641084329406
Database cluster state: in production
#恢复正常写入
testdb=# INSERT into t1 values(4);
testdb=# SELECT * FROM t1;
id
----
1
3
4
testdb=# SELECT count(*), sum(id) FROM t1;
count | sum
-------+-----
3 | 8
2.5backup备份服务器
bash
#在 backup机器上检查恢复状态
postgres=# SELECT pg_is_in_recovery();
pg_is_in_recovery
-------------------
f #已退出恢复模式,变成独立主库
(1 row)
postgres=# SELECT pg_last_wal_receive_lsn();
pg_last_wal_receive_lsn
-------------------------
#主库停了,不再接收
(1 row)
postgres=# SELECT pg_last_wal_replay_lsn();
pg_last_wal_replay_lsn
------------------------
0/24084FD0 #已回放完所有 WAL
(1 row)
postgres=# SELECT txid_current();
txid_current
--------------
834 #稳定,不再增长
#Standby 已追上主库,自动提升为主库
#在测试的还原服务器进行还原
[postgres@backup ~]$tar xf /apps/pgsql/backup/base.tar -C /apps/pgsql/data/
[postgres@backup ~]$tar xf /apps/pgsql/backup/pg_wal.tar -C /apps/pgsql/archive/
#查看故障点事务ID
[postgres@backup ~]$pg_waldump /apps/pgsql/archive/00000001000000000000002C
rmgr: XLOG len (rec/tot): 30/ 30, tx: 0, lsn: 0/2C000028, prev 0/2B000130, desc: CHECKPOINT_REDO wal_level replica
rmgr: Standby len (rec/tot): 50/ 50, tx: 0, lsn: 0/2C000048, prev 0/2C000028, desc: RUNNING_XACTS nextXid 837 latestCompletedXid 836 oldestRunningXid 837
rmgr: XLOG len (rec/tot): 114/ 114, tx: 0, lsn: 0/2C000080, prev 0/2C000048, desc: CHECKPOINT_ONLINE redo 0/2C000028; tli 1; prev tli 1; fpw true; wal_level replica; xid 0:837; oid 40973; multi 1; offset 0; oldest xid 731 in DB 1; oldest multi 1 in DB 1; oldest/newest commit timestamp xid: 0/0; oldest running xid 837; online
rmgr: XLOG len (rec/tot): 34/ 34, tx: 0, lsn: 0/2C0000F8, prev 0/2C000080, desc: BACKUP_END 0/2C000028
rmgr: XLOG len (rec/tot): 24/ 24, tx: 0, lsn: 0/2C000120, prev 0/2C0000F8, desc: SWITCH
#修改配置文件postgresql.conf,或者postgresql.auto.conf文件也可以
[postgres@backup ~]$vim /apps/pgsql/data/postgresql.conf
#加下面三行
restore_command = 'cp /apps/pgsql/archive/%f %p'
recovery_target_xid = '838'
recovery_target_action = 'promote'
[postgres@backup ~]$pg_ctl stop -D /apps/pgsql/data/ -m fast
[postgres@backup ~]$rm -f /apps/pgsql/data/postmaster.pid
#启动服务
[postgres@backup ~]$pg_ctl start -D /apps/pgsql/data/
#检查恢复日志
2026-10-03 00:41:48.527 CST [2713] FATAL: could not connect to the primary server: connection to server at "10.0.0.18", port 5432 failed: Connection refused
Is the server running on that host and accepting TCP/IP connections?
cp: cannot stat '/apps/pgsql/archive/00000002.history': No such file or directory
2026-10-03 00:41:48.534 CST [2566] LOG: waiting for WAL to become available at 0/2D000018
qcp: cannot stat '/apps/pgsql/archive/00000001000000000000002D': No such file or directory
2026-10-03 00:41:53.535 CST [2716] FATAL: could not connect to the primary server: connection to server at "10.0.0.18", port 5432 failed: Connection refused
Is the server running on that host and accepting TCP/IP connections?
cp: cannot stat '/apps/pgsql/archive/00000002.history': No such file or directory
2026-10-03 00:41:53.541 CST [2566] LOG: waiting for WAL to become available at 0/2D000018
^C
[postgres@backup ~]$tail -f /apps/pgsql/log/postgresql-*.log
#停止当前恢复进程
[postgres@backup ~]$pg_ctl stop -D /apps/pgsql/data/ -m immediate
#先确认 WAL 文件在哪
[postgres@backup ~]$ls -l /apps/pgsql/data/pg_wal/00000001000000000000002D 2>/dev/null
-rw------- 1 postgres postgres 16777216 Sep 16 19:58 /apps/pgsql/data/pg_wal/00000001000000000000002D
# WAL 文件在 pg_wal 目录,直接修复配置
#修改 postgresql.conf
[postgres@backup ~]$vim /apps/pgsql/data/postgresql.conf
restore_command = 'cp /apps/pgsql/data/pg_wal/%f %p'
recovery_target_xid = '838'
recovery_target_action = 'promote'
[postgres@backup ~]$pg_ctl start -D /apps/pgsql/data/
#验证数据
postgres=# \c testdb
testdb=# select * from t1;
id
----
1
#清理恢复配置
#删除或注释以下行:
#restore_command = 'cp /apps/pgsql/data/pg_wal/%f %p'
#recovery_target_xid = '838'
#recovery_target_action = 'promote'
#重启
[root@backup ~]#su - postgres
[postgres@backup ~]$pg_ctl restart -D /apps/pgsql/data/
#当前无法写入
testdb=# INSERT INTO t1 VALUES(3);
ERROR: cannot execute INSERT in a read-only transaction
[postgres@backup ~]$pg_controldata
pg_control version number: 1700
Catalog version number: 202406281
Database system identifier: 7675300641084329406
Database cluster state: in archive recovery
#恢复正常模式
testdb=# select pg_wal_replay_resume();
pg_wal_replay_resume
----------------------
[postgres@backup ~]$tar -tf /apps/pgsql/backup/base.tar | grep -E "standby|postgresql"
postgresql.auto.conf
postgresql.conf
standby.signal
# 删除 standby.signal
[postgres@backup ~]$rm -f /apps/pgsql/data/standby.signal
#重启
[postgres@backup ~]$pg_ctl restart -D /apps/pgsql/data/
#验证
testdb=# INSERT INTO t1 VALUES(3);
INSERT 0 1
testdb=# SELECT * FROM t1;
id
----
1
3
[postgres@backup ~]$pg_controldata
pg_control version number: 1700
Catalog version number: 202406281
Database system identifier: 7675300641084329406
Database cluster state: in production
testdb=# SELECT * FROM t1;
id
----
1
3
4
testdb=# SELECT count(*), sum(id) FROM t1;
count | sum
-------+-----
3 | 8
#数据完整,恢复成功