PostgreSQL PITR实现时间点误删除恢复

时间点恢复(PITR)是PostgreSQL中一个强大的功能,允许将数据库还原到任何特定的时间点,而不仅仅是还原到上次完整备份的状态。时间点恢复将基础备份与预写日志(WAL)文件的连续归档相结合,使PostgreSQL数据库能够恢复到自基础备份以来的任何时间点。

一、PITR 的含义

1.1一句话定义

‌PITR = Point-In-Time Recovery(时间点恢复)‌
是一种数据库灾难恢复技术,通过 ‌
"基础备份 + 连续归档日志重放"‌,将数据库精确恢复到过去‌任意指定时间点
‌的状态。

1.2拆解这个缩写

英文 中文 含义
‌P‌oint 时间点 精确到秒/事务的某一时刻
‌I‌n 在 在这个时刻
‌T‌ime 时间 不是恢复到"备份那天",而是任意时刻
‌R‌ecovery 恢复 把数据回滚/重放到那个状态

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


#数据完整,恢复成功
相关推荐
IvorySQL2 小时前
PostgreSQL 日报| relchecks 溢出导致表无法删除(9 月 28 日)
数据库·人工智能·ai·postgresql·区块链
cpolar技术支持19 小时前
本地数据库客户端只能自己用?用 cpolar 安全访问 NAS 上的 PostgreSQL 管理台
postgresql·内网穿透·cpolar·nas·pgadmin
l1t1 天前
DeepSeek总结的pg_clickhouse 与 chdb 更新:编码、嵌套与类型
数据库·clickhouse·postgresql
余槐i3 天前
EXPLAIN 显示走了索引查询仍慢:回表与选择性在 500 万行表上拉开 10 倍耗时
数据库·postgresql·性能优化·vacuum·explain
梦在远山后3 天前
PostgreSQL 锁与死锁:结合 DevMind 讲清楚
python·postgresql·agent
浪潮IT馆3 天前
Windows 10 安装 PostgreSQL 9.6.24 完整教程
数据库·windows·postgresql
岳麓丹枫0013 天前
PG 孤儿物理复制槽残留导致 pg_wal 膨胀 19G 故障分析
postgresql
haerapi4 天前
把 PostgreSQL 复制巡检做成可审计闭环:确定性采集、阈值判定与受控模型归纳
数据库·postgresql
如果'\'真能转义说4 天前
Docker Desktop | 本地化挂载 Postgresql
docker·postgresql·容器