news 2026/10/21 6:38:00

PostgreSQL物理备份与恢复实战:pg_basebackup + WAL归档

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
PostgreSQL物理备份与恢复实战:pg_basebackup + WAL归档

你手头那个 PostgreSQL 库,如果现在挂掉,你有多大的把握在一个小时内完整恢复?我在生产环境里见过不少同事,平时聊天说起备份头头是道,真到出问题的时候,翻备份才发现要么是逻辑备份漏了序列,要么是归档 WAL 断了好几天,折腾到凌晨两三点还在对着报错发愁。后来我给自己定了一条规矩:所有的 PostgreSQL 实例,只要上了生产,就必须有 pg_basebackup 的物理备份兜底。这条规矩救过我至少三次。

作为 PostgreSQL 原生的备份工具,pg_basebackup 干的事情其实很纯粹:把整个数据目录做一次一致性快照,包括数据文件、日志、配置文件,全部原样打包。和 pg_dump 那种把数据倒腾成 SQL 的逻辑备份不同,它不关心你库里有多少张表、什么结构、什么权限,直接把文件层面复制一份,恢复的时候也简单,把文件放回去,启动就是一个完整的实例。这种“整机搬家”式的备份逻辑,在灾难恢复场景下特别占优势——数据库集群啊、表空间啊、大对象啊、自增序列啊,统统不用单独处理,一次全搞定了。

这篇文章,把我这些年用 pg_basebackup 做物理备份、结合 WAL 归档做时间点恢复的实操经验整理一遍。从原理到参数,从备份命令到恢复演练,再到踩过的那些坑,争取一次说透。无论你是刚入门的 DBA,还是在创业公司里被迫“全栈运维”的后端开发,照着做,至少能保证数据不再裸奔。

1. 为什么是 pg_basebackup:物理备份和逻辑备份的差异

1.1 备份的两个派别:先想清楚你要防什么

PostgreSQL 的备份方案,本质上分成两个思路。逻辑备份,典型的像 pg_dump、pg_dumpall,它做的事是“读懂”数据库里的数据,把表和约束以 SQL 或者自定义格式导出来。物理备份则完全不同,它不去理解业务数据结构,直接复制底层文件,pg_basebackup 就是干这个的。

这两个派别适用场景差别很大。如果你的目的是把某个表的数据导出来给另一个环境用,或者做跨大版本迁移,逻辑备份更灵活。但如果是防“整个实例崩溃”“服务器磁盘损坏”“机房断电导致文件系统受损”这类极端事故,物理备份才是真正的底牌。因为逻辑备份恢复的时候,你得先搭一个空的 PostgreSQL 实例,再执行导入,这个过程动辄以小时计,而且遇到存储过程、序列当前值、权限这类对象时,经常出现导不全的问题。物理备份则是把整个数据目录原封不动放回去,多复杂的实例都能拉起来。

我在实际工作中见过一个典型案例:某公司的库里有几个自定义类型,还建了一堆序列,逻辑备份的时候没注意,恢复后序列当前值全回到了初值,结果业务跑了一晚上,第二天主键冲突把应用打挂了。物理备份就没有这种问题,数据文件什么样,恢复之后就是什么样,序列、类型、权限,全部都跟着走。所以有句话说得很准确:逻辑备份是“导出数据”,物理备份是“克隆实例”。

1.2 pg_basebackup 的热备原理:它比 tar 好在哪

有人可能会问:既然物理备份就是复制文件,那我直接停库,tar 打包整个数据目录,不一样吗?对这种“冷备份”方式,历史上确实有不少老牌 DBA 在用,它本身没有错,但代价是你要停服——这在生产环境往往不可接受。

pg_basebackup 能做到“不停机备份”,靠的是 PostgreSQL 的复制协议。它从原理上把自己模拟成一个备库角色,连接到主库,主库开始给你发送数据文件副本。关键点在于,备份过程中数据库仍然有写入,主库会把这段时间产生的 WAL(预写日志)持续传送或保存下来,保证备份出来的文件集在时间轴上是一致的。等主库把数据文件全量发完了,pg_basebackup 再追平增量 WAL,这时得到的备份,就是一个站得住脚的完整数据目录。

用大白话说,pg_basebackup 相当于在“车还在跑”的时候,给车拍了一张特快照片,拍完了还把车在拍照过程中开出去的距离补齐了。相比之下,直接 tar 运行中的数据库目录,就像在车跑动的时候试图伸手去抓驾驶座上的文件,抓回来的东西本身是前后矛盾的,根本没法定性。

理解这个原理特别重要,因为后面配置 WAL 归档、设置备份参数时,很多选项都是为了配合这套机制运行的。

1.3 什么时候不要用 pg_basebackup

任何工具都有边界。pg_basebackup 虽然强大,但我的使用原则是“物理备份兜底,逻辑备份补充”,两者并不冲突。

pg_basebackup 不适合干什么?第一,跨大版本恢复,比如从 PG 12 备份恢复到 PG 16,文件格式不兼容,物理备份走不通,只能用 pg_dump 逻辑迁移。第二,只恢复业务里面的某几张表,物理备份会把整个实例都覆盖掉,没法精细到表级别,这种场景用 pg_dump 或者搞一个独立的 schema 更好。第三,备份频率极高、数据量极大时,每次都全量拷贝数据目录,成本很高,这时候可以考虑加上增量备份的第三方工具(如 barman 之类的调度工具),但底层依然会调用物理备份的基础能力。

认清边界之后,pg_basebackup 在备份体系里就扮演了那个“无论如何都要有”的角色:不管你怎么做逻辑备份,反正每周至少要有一次物理备份,而且恢复演练必须常态化验证。

2. 准备阶段:参数、账号、目录,先把地基打好

2.1 关键参数配置:wal_level、max_wal_senders、wal_keep_size

使用 pg_basebackup 之前,需要先确认 PostgreSQL 实例本身开启了正确的能力,不然命令会直接报错,或者备份后无法做时间点恢复。

最核心的是wal_level。PostgreSQL 的默认值是replica(旧版本可能是minimal),如果当前值是minimal,物理备份可以勉强产生,但是备份后如果只靠这一个基础备份,没有 WAL 归档配合,恢复能力会大打折扣。考虑到备份的意义不只是“留一个文件”,更是为了“能恢复到指定时间点”,建议至少设置为replica,这个级别已经开启了足够的 WAL 信息,支持复制和物理备份。如果你以后想用逻辑复制(如基于逻辑解码的同步),就得设成logical。

然后是max_wal_senders。pg_basebackup 本质上是发起一个复制连接,需要占用一个 WAL sender 进程。默认值往往只有 10,如果你不仅有备份任务,还有两个流复制备库,再加一些临时拉取 WAL 的任务,10 个可能不够。我习惯在生产环境设置成 20 以上,给备份任务留出富余。修改这个参数需要重启实例生效,所以最好在建库初期就规划好。

最后一个常被忽略的是wal_keep_size。这个参数控制主库保留多少 WAL 段,防止在备库或备份工具还没来得及拉走日志时,旧 WAL 就被回收。备份单次执行时可能需要追平一段增量 WAL,如果主库清理太快,备份会中断。旧版本叫wal_keep_segments,按段数算;新版本直接按大小算,更直观。生产环境我通常设 1GB 到 4GB 之间,配大并发写入场景,太少了备份时容易追不上。

2.2 备份账号与 pg_hba.conf 配置

pg_basebackup 连接主库时,需要使用具备超级用户权限或至少REPLICATION权限的账号。在日常实践里,我建议创建一个专用账号,不要直接拿超级用户顶着用。

创建账号的命令很简单:

CREATE USER backup_user WITH REPLICATION PASSWORD '一个足够复杂的密码';

这里有个细节,REPLICATION权限意味着这个账号可以拉取主库的 WAL 流。在备份场景下它是必要条件,但安全上要严格管控,密码建议用强随机串,并且只在备份机与数据库之间范围内允许访问。

接着修改pg_hba.conf,增加一行允许备份机连接:

# 备份机使用 replication 连接 host replication backup_user 192.168.1.0/24 md5

注意这里用的是replication关键字,和普通的数据库连接区分开。改完之后执行pg_ctl reload,不需要重启实例。

当时我第一次配 pg_basebackup 时漏了这条规则,结果命令一执行就报could not connect to server,排查了半天才发现是 pg_hba.conf 没加 replication 行,白折腾了两个小时。这种坑非常典型,建议所有读者在配备份账号时,第一件事就是检查这个位置。

2.3 磁盘空间估算:不要等爆盘了才后悔

pg_basebackup 的备份文件体积,和实际数据目录大小差不多,如果是压缩格式则更小。但有个容易忽略的点:备份仓库存放目录、归档 WAL 目录、以及本地数据目录,这三个地方不能挤在一个磁盘,至少不要挤到同一个挂了就全完蛋的物理盘。

估算磁盘空间时,我一般用这个公式:

保留空间 ≈ 数据目录物理大小 × 备份份数 + 每日WAL量 × 保留天数 + 10% 余量

比如数据目录 500GB,每日产生 WAL 约 20GB,保留最近 7 份基础备份和 14 天 WAL,那么至少准备500×7 + 20×14 + 10% ≈ 3800GB的空间,实际使用可能需要 4TB 以上。当然,如果开启压缩,基础备份会小不少,但我向来按不压缩的上限来规划,宁可富余一点,也不要某天早上发现备份因为磁盘空间不足而静默失败。

3. 实操全流程:从一次干净的物理备份到时间点恢复

3.1 执行 pg_basebackup 的常用命令模板

看过太多网上贴的零碎命令,真正能直接用的模板其实是固定的。下面是我常用的命令,参数都做过实际验证:

pg_basebackup \ -h 192.168.1.10 \ -p 5432 \ -U backup_user \ -D /backup/base/$(date +%Y%m%d) \ -Ft -z \ -P \ -R \ -l "backup-$(date +%Y%m%d-%H%M%S)"

逐一解释关键参数:

  • -Ft:输出格式为 tar 包,打包成一个文件;如果不加,会直接生成一个和源数据目录结构一致的文件树,适合后续直接作为备库数据目录。
  • -z:对 tar 包压缩,减少磁盘占用。备份速度会慢一点,但通常可接受。
  • -P:显示备份进度,包括传输的数据量和总体时间。实测连到远程大库时,这个参数能让你判断到底是在跑还是在卡住。
  • -D:目标目录,tar 格式时是输出文件的位置,文件树格式时是目标目录本身。
  • -R或--write-recovery-conf:生成一个standby.signal文件并把主库连接信息写入postgresql.conf.auto。注意,这个参数只在你要把这个备份当作备库使用时才需要加。如果只是为了归档恢复,千万不要加,否则恢复时会进入备库模式。

执行完毕后,检查目标目录里有没有生成类似base.tar.gz的文件,以及终端日志是否显示COPY完成。备份过程中新产生的 WAL 段,也会自动追平。

3.2 恢复流程三步走:解压、放 WAL、指定恢复目标

备份做完了,最关键的还是“能恢复”。恢复流程看起来简单,但每一步都有讲究。

第一步,把 tar 包解压成完整的数据目录。比如刚才备份生成的是base.tar.gz,先停掉 PostgreSQL 服务,清空目标数据目录(千万别放在原目录上,要放到一个干净路径里),然后解压:

mkdir -p /data/pgdata_restored tar -xzf /backup/base/20260620/base.tar.gz -C /data/pgdata_restored

注意解压之后,要检查数据目录里是否有pg_wal这个目录(新版本叫pg_wal,老版本叫pg_xlog)。pg_basebackup 备份时,这个目录默认是空的,因为 WAL 要从归档里补。如果你备份前没开归档,备份完成后到恢复之间产生的 WAL 已经不在主库了,那这个基础备份只能恢复到备份完成的那一刻,稍微晚一点的时间点都回不去。这就是我一直强调“基础备份必须配 WAL 归档”的根本原因。

第二步,把需要恢复到的那个时间点之前的 WAL 文件,放到pg_wal目录里。如果 WAL 在归档目录里,直接复制过来,或者把它们软连接/指向到恢复位置都可以。操作上我习惯先把归档目录准备好,全部复制过去,不放心的可以对比文件的 checksum 确保完整性。

第三步,在数据目录里创建recovery.signal文件(PG 12 及以上版本;旧版本是recovery.conf),并在postgresql.conf里设置恢复目标。假如我想恢复到 2026-06-20 13:44:00,也就是突然发生误操作的时间点之前,可以这样配置:

recovery_target_time = '2026-06-20 13:44:00'

启动 PostgreSQL 后,实例会进入恢复模式,应用归档里的 WAL 到目标时间点,然后自动停在那个位置。这时候数据就处于目标时间点的一致状态了。如果恢复成功,你会看到日志里出现recovery stopping before commit/abort of transaction之类的信息。确认数据无误后,执行:

SELECT pg_wal_replay_resume();

其实 PG 12 以后的流程是:恢复完成后实例会自动进入可读状态,如果确认没问题再 promote,把实例从恢复模式转为正式可写。命令是把recovery.signal文件删掉再重启,或者执行SELECT pg_promote();

3.3 一个小实验:利用 PITR 恢复到误删除数据前的一刻

为了让大家真正理解这套流程,我把一次误删除恢复的演练过程记一下,非常直观。

假设某测试实例,库里有一张订单表orders,正常数据量大概一万行。我在 13:43 执行了一条删除语句,误删了所有订单。业务方在 13:45 打电话来:能不能恢复到 13:42 之前的数据?

下面是我的处理动作:

  1. 确认该实例开了 WAL 归档,归档目录里保存着 13:43 之前的连续 WAL。
  2. 停止对这个实例的写入(这里因为是测试环境,直接把应用断开了;正式环境最好先只读冻结业务,或者干脆再拉一个从库来演练)。
  3. 找到最近的一个 pg_basebackup 基础备份,假设是当天凌晨 02:00 做的,解压到一个新目录。
  4. 把归档目录里从 02:00 之后、到 13:42 之前的所有 WAL 段,复制到恢复实例的pg_wal目录。
  5. 在恢复实例的postgresql.conf里设置recovery_target_time = '2026-06-20 13:42:00',创建recovery.signal文件。
  6. 启动恢复实例,等待日志显示恢复完成。
  7. 查询订单表,数据还在,而且行数与 13:42 时的快照一致。
  8. 执行pg_promote(),把恢复实例切成正式实例,继续支撑业务。

整个过程熟练操作的话,十几分钟内能完成恢复,对业务的影响控制在极短时间窗口内。这就是“基础备份 + WAL 归档 + pg_basebackup”这套方案真正值钱的地方:它把恢复时间目标(RTO)做到了可以用分钟级衡量。

4. 常见问题与避坑经验

4.1 备份失败?先查这三张王牌配置

pg_basebackup 报错种类有限,但每次报错都让人头大。根据我的排查经验,最常见的原因有三个:

第一,pg_hba.conf 没有配置 replication 行。报错一般是could not connect to server: FATAL: no pg_hba.conf entry for replication connection。解决办法就是按上文 2.2 节加一行并 reload。

第二,磁盘空间不足。报错一般是在 COPY 过程中传输到一半突然中断,日志里会有No space left on device。这个必须提前规划。

第三,权限问题。备份目录的属主如果不是运行 PostgreSQL 的系统用户,可能写不进去。备份机上的目录建议 chown 给 postgres 系统用户(或者你跑 pg_basebackup 那个系统用户)。

另外提醒一句,如果备库也在使用pg_basebackup做克隆,主库的max_wal_senders不够用时会报insufficient number of WAL senders,这种可以通过调整参数提前规避。

4.2 恢复后启动失败怎么办

恢复流程中,启动实例失败是个常见坎。最常见的两类情况我这里列一下。

一类是找不到recovery.signal文件,或者旧版本里没有recovery.conf。实例启动后直接进入正常模式,根本没有应用 WAL,更不可能停在目标时间点。解决方法是:创建空文件recovery.signal放在数据目录里,然后重启。

另一类情况是 WAL 文件放的位置不对。归档目录一般位于/backup/wal之类的独立位置,如果不复制到实例的pg_wal目录,或者没有正确配置archive_command去引导实例读取,启动会报requested WAL segment ... has already been removed之类的错误。这时候需要检查恢复实例的postgresql.conf里restore_command是否指向了归档目录。比如:

restore_command = 'cp /backup/wal/%f %p'

有些人喜欢用pg_archivecleanup来配合,也可以用,但注意路径别写错。我见过有人因为%f和%p顺序搞反,折腾了一个下午都没恢复成功。

恢复后实例是只读的,这个不是问题,不代表失败。在恢复目标时间内查询完数据后,要主动 promote 才会变成可写。

4.3 备份脚本的定时任务与安全性建议

手动敲 pg_basebackup 只是入门,真正的生产实践应该是脚本化。我开发了下面这样一个精简的备份脚本框架,供大家参考:

#!/bin/bash # 每周日凌晨执行一次 pg_basebackup BACKUP_BASE=/backup/base DATE_TAG=$(date +%Y%m%d-%H%M) LOG_FILE=${BACKUP_BASE}/backup_${DATE_TAG}.log mkdir -p ${BACKUP_BASE} pg_basebackup \ -h 192.168.1.10 \ -U backup_user \ -D ${BACKUP_BASE}/${DATE_TAG} \ -Ft -z -P > ${LOG_FILE} 2>&1 # 判断执行结果 if [ $? -eq 0 ]; then echo "pg_basebackup succeeded at $(date)" >> ${LOG_FILE} # 保留最近7天的备份,防止磁盘占满 find ${BACKUP_BASE} -maxdepth 1 -type d -mtime +7 -exec rm -rf {} \; else echo "pg_basebackup failed at $(date)" >> ${LOG_FILE} fi

这个脚本的核心逻辑是:执行备份、记录日志、清理过期备份。注意脚本里我保留 7 天,如果数据重要,建议保留更长时间,甚至同步到其他存储做异地容灾。

除了脚本本身,还要强调安全性:备份目录权限建议只有数据库系统用户可读写。因为备份文件里包含几乎完整的数据库文件,任何人拿到它就能把整个实例恢复出来,权限设得太宽等于把数据拱手送人。

还有一点,pg_basebackup一旦执行,建议不要在数据库业务高峰时段进行,除非你确认磁盘 IO 和网络带宽非常充裕。曾经有人白天跑大表备份,结果把生产网络打满了,应用响应全线变慢,最后被业务方投诉。所以排定时任务时,要么凌晨低峰,要么用pg_basebackup --rate-limit(新版本支持限速)把带宽占用量控制住。

4.4 经验速查表:备份与恢复常见问题对照

为了大家快速定位问题,我整理了一张实用对照表,基本覆盖了我在实战中遇到的大多数情况:

问题表现可能原因处理方式
连接主库被拒绝,报 replication 错误pg_hba.conf 缺 replication 行添加规则并 reload
备份过程中显示复制进度停滞主库 WAL 被提前清理调大 wal_keep_size 或确认归档正常
目标目录已存在且非空目录残留历史文件清空目录或用独立路径
恢复后库是只读的处于恢复模式,属于正常数据确认后执行 promote
恢复时 WAL 一直找不到restore_command 路径不对检查配置确认归档路径可达
备份占满磁盘没有定期清理旧备份脚本加入 find/mtime 清理

4.5 关于备份验证这件事,千万不要跳过

虽然标题是“物理备份”,但我必须单独强调一个环节:验证备份。备份文件不是生成了就完了,如果恢复过不了,备份就是一堆废文件。我见过不少团队,备份策略定得很高,每天保留好几份,但从来没做过一次真正的恢复测试。真到出事故那天,才发现备份文件因为磁盘坏道已经损坏,或者根本没法用了。

我的做法是,每份基础备份生成后,要么在临时实例上完整恢复一次,抽查备份文件的完整性;如果环境不允许频繁恢复,也至少用一个装了相同版本的 PostgreSQL 的备用机器,把备份解压,拉起来看能不能正常查询。这个动作看着简单,实际上是对“物理备份”方案负责。

实测中我发现,做过完整恢复演练的备份,在灾难恢复时的成功率和速度都远超没演练过的。别存侥幸心理,恢复能力不是靠想象出来的。

4.6 几个容易忽略但会影响大局的细节

最后分享几个实战里的小细节,都很零碎,但恰恰能踩人:

  • pg_basebackup备份出的 tar 包,恢复前最好先验证文件完整性和 crc 校验,如果 tar 损坏,恢复时很容易半道报错。
  • 备份过程中不要手动删除归档目录里的 WAL,否则恢复时可能因为缺段失败。
  • 带-R参数生成的备份如果直接当独立实例恢复,会默认走进备库模式,注意删掉standby.signal或根据需求调整。
  • 如果在 Docker 环境里做恢复,注意pg_wal目录在容器里是特殊卷的话,文件可能不会落到持久化存储,恢复失败就毫无头绪。
  • 备份账号本身也要定期轮换密码,不能因为“只读备份”就放松管理。
  • 恢复演练时,一定要开一个和线上版本完全一致的 PostgreSQL 实例,版本不完全一致时,文件格式不匹配会导致恢复失败。

我把这些坑一个个踩过来之后,才真正理解为什么老 DBA 总说“备份这件事,越早规划越省心,越晚验证越翻车”。与其等故障发生时才想补救方案,不如现在就拿起 pg_basebackup,跑一版备份,做一次恢复演练,把“能不能恢复”这个问题的答案,从猜测变成确定。

如果你也是第一次配置,我建议从今天开始,搭一个测试实例,按文中的命令亲手做一遍备份和恢复。等那些参数、命令、错误日志都成了你肌肉记忆的一部分,再遇到生产故障,你就不会慌。恢复数据这件事,从来不是“运气好”三个字能解释的,而是每一次备份、每一行配置、每一次演练攒下来的底气。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/21 6:37:31

Windows 下 Nexus 3.45.0 搭建 Maven 私服实战指南

简介:nexus-3.45.0-01-win64 是 Sonatype 官方推出的开源仓库管理器 Nexus 在 Windows 64 位平台上的完整安装包,面向 Java 开发者、DevOps 工程师及需要搭建 Maven 私服的团队。它可作为内部受控仓库,集中存储、代理与分发构建工件&#xff…

作者头像 李华
网站建设 2026/10/21 6:37:28

SpringBoot+Vue+MySQL在线文档管理系统毕设完整实战指南

做毕业设计选在线文档管理系统这个题,SpringBootVueMySQL 这套组合几乎成了标准答案,每年都有大量学生做类似项目。但正因为做的人多,“能跑”和“做得好”之间的差距就特别明显。如果一个系统只有最基本的上传下载,答辩老师一眼就…

作者头像 李华
网站建设 2026/10/21 6:35:58

知识工作插件化:构建可编程、可组合的智能工作流底座

1. 项目概述:这不是一个插件,而是一套知识工作者的“操作系统增强层”“knowledge-work-plugins”这个标题乍看像某个开源仓库的命名,但拆开来看——knowledge work(知识工作)、plugins(插件)—…

作者头像 李华
网站建设 2026/10/21 6:35:26

uniapp考试答题小程序源码解析:从题库加载到自动判分

简介:这是一份基于uniapp框架开发的考试答题类小程序源码,面向希望学习跨平台应用开发、了解前端答题逻辑的开发者与在校学生。项目实现了单选、多选、判断三种题型的答题界面,并支持上一题、下一题的自由切换,题目与答案均以静态…

作者头像 李华
网站建设 2026/10/21 6:34:49

STM32+OpenMV电赛巡线小车:从看懂到敢改的完整工程拆解

简介:这份资源是2024年全国大学生电子设计竞赛H题巡线小车的完整工程,面向备赛电赛控制类赛题的学生与嵌入式爱好者,主控采用STM32,配合OpenMV摄像头实现循迹方案。由于作者缺少官方器材,工程以摄像头巡线替代灰度传感…

作者头像 李华
网站建设 2026/10/21 6:34:48

基于OpenCVSharp的角点检测实现工业平面度测量

做工业检测的人对“平整度”三个字应该都不陌生。我之前接了一个项目,要给一批金属面板做平面度初筛,预算有限、又不能上激光轮廓仪,最后是拿一台普通工业相机加OpenCVSharp的角点检测把这活儿干的。当时最头疼的是网上资料大多在讲相机标定、…

作者头像 李华