半夜两点,电话铃声响起来,我第一反应是PostgreSQL又出事了。值班同事声音有点慌:"主库磁盘满了,归档目录写不进去了,WAL快堆积爆了。"这种时刻,最能检验一个数据库环境有没有做好准备:物理备份是否完整,从库能不能顶上。
这篇文章要聊的就是这两件事——PostgreSQL物理备份和从库搭建。很多朋友把这两个概念分开看,觉得备份是备份,高可用是高可用,但在生产环境里它们是一套组合拳:物理备份是最后的兜底,从库是秒级的热备与读流量承接者。文章会讲清楚物理备份的原理和实操、pg_basebackup拉基础备份的完整过程、流复制从库从配置到验证的每一步,以及我在生产环境里踩过的那些备份相关的坑。适合自己维护PostgreSQL的DBA、后端开发、运维同学参考,不管你的库是几十GB还是几个TB,这套思路都适用。
1. 为什么要把"备份"和"从库"放在一起考虑
先想清楚一个问题:有了从库,还需要物理备份吗?很多团队搭完流复制从库之后,觉得高可用有了,备份就可以敷衍了事。这个想法非常危险。
1.1 从库不是备份
从库的本质是主库的一个持续复制的副本。它通过WAL日志重放,保持和主库近乎实时的数据同步。看起来从库有全量数据,好像就是一个"活的备份"。但关键在于,从库和主库是同一个逻辑故障面的——一条错误的DELETE语句在主库执行,会通过流复制原样重放到从库;一个被损坏的数据页,也会从主库同步到从库;一次误操作导致的逻辑数据丢失,从库大概率也会跟着丢。
物理备份则不同。它是某个时间点的一个一致性快照,独立于主库存在。加上归档的WAL日志,你可以把数据库恢复到过去任意一个时间点——不管是半小时前、昨天,还是上周。这就是从库无法替代的"后悔药"属性。
1.2 冷备、温备、热备的定位
从库本质上是一种热备形式,随时可读,但它是"逻辑上的热备",不是"物理上的独立副本"。真正的高可用方案,通常是这样分层的:
| 层级 | 手段 | 恢复时间目标 | 数据丢失容忍度 |
|---|---|---|---|
| 第一层 | 从库 | 秒级到分钟级 | 极小(取决于同步模式) |
| 第二层 | 物理备份 + WAL归档 | 分钟级到小时级 | 可配置,基本为零 |
| 第三层 | 异地备份副本 | 按需 | 零 |
你会发现,从库负责的是"快速恢复服务",物理备份负责的是"精确找回数据"。两者缺一不可。这篇文章按这个思路,先把物理备份讲透,再讲从库搭建,最后聊聊故障切换里那些容易被忽略的细节。
1.3 适合谁按这套方案走
如果你的PostgreSQL是云数据库托管服务,厂商一般已经替你做了物理备份和从库,这篇文章可以帮你理解底层原理,出了问题也能更好地和厂商技术支持沟通。如果你是自建PostgreSQL,尤其是线上业务核心库,那物理备份和从库搭建应该作为基础设施标配,这篇文章可以直接当作操作手册来用。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 物理备份和逻辑备份的差别,以及为什么多数生产环境选物理备份
很多人一开始接触备份,用的都是pg_dump。这没问题,小库用pg_dump完全可行,但库一旦长到一定规模,pg_dump就会开始让人头疼。
2.1 物理备份的本质
物理备份,简单说就是直接复制PostgreSQL的数据目录。数据目录里有base(各数据库的文件)、pg_wal(WAL日志)、pg_xact(事务状态)、pg_control(控制文件)等。物理备份做的就是把这些文件"原样"拷贝一份,同时配合WAL归档,保证备份文件是一致的、可恢复的。
这里有一个关键概念:在线物理备份为什么能做到"备份过程中数据库还在写",但备份出来的文件却是一致可用的?答案是全量备份 + WAL重放。pg_basebackup会先做一次基础备份(base backup),这个过程可能耗时很长,期间数据库一直在产生新数据。备份工具会记录一个起始点,把过程中产生的新WAL一并收进来。恢复的时候,先恢复基础备份,再重放WAL,就能把数据库推进到一致性状态。
2.2 pg_dump和pg_basebackup的对比
| 对比项 | pg_dump(逻辑备份) | pg_basebackup(物理备份) |
|---|---|---|
| 备份内容 | SQL语句/数据行 | 整个数据目录文件 |
| 恢复速度 | 慢,需要逐条执行SQL | 快,文件拷贝即可 |
| 一致性 | 可指定,多表间需额外处理 | 天然一致 |
| 占用空间 | 较小 | 较大 |
| 支持粒度 | 可选择表、库、schema | 整个实例 |
| 恢复粒度 | 可恢复到表级 | 只能全实例恢复 |
| 适用规模 | 小库、部分数据导出 | 中大库、生产库 |
实际生产里,几十GB以内的库用pg_dump还能接受,超过这个量级,全量导出再导入的时间成本、空间成本都会显著上升。而且pg_dump恢复时是单进程逐条执行的,恢复一个大库可能要跑好几个小时,这对线上恢复来说太慢了。
2.3 物理备份工具选型
PostgreSQL官方的物理备份工具是pg_basebackup,它内置于PostgreSQL本体,不需要额外安装。它的实现依赖复制协议,通过流复制的方式把数据文件从主库拉下来。很多第三方备份工具,比如pgBackRest、pg_rman,底层思路也是类似的,但会额外做增量备份、加密、压缩、远程存储等增强功能。
我个人的建议是:如果只是搭从库或者做基础备份,pg_basebackup足够了;如果你要一个完整的备份策略,包括每日全量、增量、备份保留策略、远程存储,那用pgBackRest会更省心。这篇文章先聚焦pg_basebackup,毕竟它是理解整个物理备份机制的基石。
3. pg_basebackup实操:从零拉一个基础备份
pg_basebackup看起来就是一个命令,但要把参数用对、把前置条件配好,还需要理解它背后的机制。我按生产环境的标准,从配置到命令、到验证,一步步来说。
3.1 主库需要提前准备的配置
先看postgresql.conf里的几个关键参数:
conf复制# 必须开启的复制相关配置
wal_level = replica # 允许物理复制,logical也可以但不能低于replica
max_wal_senders = 10 # 允许同时多少个WAL发送进程,至少要大于从库数量+备份连接数
max_replication_slots = 10 # 复制槽数量,有备库或者用槽位备份时建议开启
wal_keep_size = 128 # 保留多少WAL,防止从库或备份追不上(值根据实际调整)
这几个参数的逻辑是这样的:wal_level决定了主库的WAL里是否包含足够的信息来支持物理复制,replica就够了;max_wal_senders限制有多少个连接能同时拉取WAL,如果你有2个从库,同时还要跑一次备份,那至少要配置3个以上;wal_keep_size或max_slot_wal_keep_size是为了防止从库短暂掉线后,主库的WAL已经循环覆盖,导致从库无法续接,只能重新拉全量备份。
还有pg_hba.conf,需要允许备份账号使用复制协议连接:
conf复制# 允许backup_user用户从内网发起复制连接
host replication backup_user 192.168.1.0/24 md5
3.2 创建备份专用用户
生产环境不建议用超级用户跑备份,单独建一个账号更安全,权限也更好控制:
sql复制CREATE USER backup_user WITH REPLICATION LOGIN PASSWORD 'your_password';
REPLICATION权限是pg_basebackup必需的。它允许这个用户发起复制连接,但不能做普通的增删改查,权限边界很清晰。
3.3 pg_basebackup命令详解
常用的命令是这样:
bash复制pg_basebackup -h 192.168.1.10 -p 5432 -U backup_user \
-D /data/pg_backup/base_20250101 \
-Fp -Xs -P -R -l "base backup 20250101"
逐个参数拆解:
| 参数 | 含义 | 说明 |
|---|---|---|
| -h | 主库IP | 如果有从库,也可以从从库拉备份,减轻主库压力 |
| -D | 备份输出目录 | 目录必须为空或不存在 |
| -Fp | 输出格式plain | 直接输出文件目录,适合立即用的备份;也可以选-Ft输出tar包,适合归档 |
| -Xs | WAL流式传输 | 备份期间产生的WAL实时流式接收完成,比-Xf(先拷贝文件再收WAL)更可靠 |
| -P | 显示进度 | 大库备份时心里有底,也能判断备份是否正常 |
| -R | 生成standby.signal | 如果这个备份是用来搭从库的,-R会自动生成流复制配置文件,后面会详细讲 |
| -l | 备份标签 | 给这个备份一个可读的名字,会在backup_label里记录 |
还有一个参数容易被忽略:-C --slot=some_name。带上-C会在主库上创建一个临时的复制槽,用来确保备份期间主库不会提前清理WAL。这个参数在生产环境强烈建议加上,尤其是大库备份耗时比较长的时候。命令变为:
bash复制pg_basebackup -h 192.168.1.10 -p 5432 -U backup_user \
-D /data/pg_backup/base_20250101 \
-Fp -Xs -P -R -C --slot=pg_basebackup_slot_20250101 \
-l "base backup 20250101"
3.4 备份完成后的验证步骤
备份拉完只是第一步,验证备份可用才是关键。不要等灾难发生后才第一次尝试恢复备份。每次备份完成后,至少要做这几件事:
首先确认备份文件完整性:
bash复制ls -lh /data/pg_backup/base_20250101/
cat /data/pg_backup/base_20250101/backup_label
backup_label文件很重要,它记录了这次备份的起始WAL位置和备份时间等信息,恢复时要靠它来定位起点。
其次,如果是用-R生成的备份,目录里会有standby.signal和postgresql.auto.conf。确认这些文件存在,并且postgresql.auto.conf里已经有primary_conninfo配置,说明这个备份可以直接当作从库的数据目录来用。
最后,有条件的话,把备份复制到一台测试机器上,启动一个临时实例,做一次恢复验证。我见过太多团队备份了一堆文件却从没验证过,真到恢复的时候发现备份损坏、缺少WAL段、文件权限不对,各种问题。恢复演练应该纳入常规运维流程,至少每季度一次。
3.5 表空间要单独注意
如果数据库用了自定义表空间,情况会复杂一点。pg_basebackup对表空间的默认处理方式是重新映射目录,因为表空间在文件系统上的实际路径,备份到新机器后可能不一样。这是物理备份最常见的坑之一。
处理方式有两种。一是备份完成后,手动调整表空间目录的软链接;二是在初始化前就规划好,让新机器上表空间路径和旧机器保持一致。我个人的建议是:生产环境尽量把表空间路径标准化、统一化,能省掉很多恢复时的麻烦。如果确实没法统一,恢复时必须认真检查$PGDATA/pg_tblspc目录下的软链接,确保都指向了正确的实际位置。
4. 从库搭建全过程:流复制配置与验证
物理备份拉下来之后,搭建从库就是顺理成章的事了。本质上,从库就是"一个持续应用主库WAL的物理备份"。理解了这句话,你就能明白整个从库机制的核心。
4.1 主库侧配置检查
在从库启动之前,主库一定要准备好。除了前面讲过的wal_level和max_wal_senders,还要检查一个容易被忽略的配置:hot_standby。
在PostgreSQL 14及更早版本中,hot_standby是独立参数。PostgreSQL 15之后,这个参数被默认启用并移除了,但如果你在维护老版本,还是要确认主库postgresql.conf里已经开启hot_standby = on。它的作用是允许从库启动后接受只读查询,否则从库只能一直"恢复",连SELECT都执行不了。
另外主库的pg_hba.conf还需要允许从库IP使用复制协议连接:
conf复制host replication repl_user 192.168.1.0/24 md5
4.2 从库专属复制用户
建议单独建一个复制用户,不要用备份用户,也不要复用业务账号:
sql复制CREATE USER repl_user WITH REPLICATION LOGIN PASSWORD 'repl_password';
这样做的原因是,以后如果要做监控、做备份、做故障切换,每个账号的用途清晰,也方便单独回收权限。比如你可以把repl_user只用于流复制,把backup_user只用于备份,互相不污染。
4.3 用pg_basebackup搭建从库:一步到位的 -R 参数
搭建从库最顺滑的方式,是用pg_basebackup直接生成一个"已经配置好流复制"的备库数据目录:
bash复制pg_basebackup -h 192.168.1.10 -p 5432 -U repl_user \
-D /data/pg_slave \
-Fp -Xs -R -C --slot=pg_slave_slot \
-l "slave setup 20250101"
执行完成后,看两个文件。第一个是/data/pg_slave/standby.signal,它的存在告诉PostgreSQL"这个实例是一个备库,启动后进入恢复模式,持续应用WAL"。第二个是/data/pg_slave/postgresql.auto.conf,里面已经自动写入了primary_conninfo,内容类似:
conf复制primary_conninfo = 'user=repl_user password=repl_password host=192.168.1.10 port=5432 sslmode=prefer application_name=pg_slave'
其中application_name建议设置,后面做主从监控和故障切换时,通过application_name可以快速识别是哪个从库。
4.4 复制槽:防止WAL提前清理的保险
上面命令里有一项-C --slot=pg_slave_slot,作用是在主库上创建一个持久化复制槽。复制槽保证一个原则:只要这个槽存在,主库就不会删除该槽对应的WAL,即使从库长时间离线。
这意味着什么?假如没有复制槽,你的从库因为网络中断离线了3小时,主库这3小时里产生的WAL如果超过wal_keep_size的保留量,旧WAL会被循环覆盖。从库恢复后会发现需要的WAL已经不存在了,唯一的办法是重新拉全量备份,这可是非常痛苦的事情。
复制槽的代价是:如果从库真的永久挂了,主库的WAL会一直累积、永不清理,最终把磁盘塞满。所以用复制槽必须配合监控:确认某个槽位对应的从库已经不再需要时,及时清理:
sql复制SELECT slot_name, slot_type, active
FROM pg_replication_slots;
SELECT pg_drop_replication_slot('pg_slave_slot');
4.5 从库的postgresql.conf调整
pg_basebackup -R只生成了standby.signal和primary_conninfo,从库还需要设置一下几个参数,否则很难用于生产:
conf复制hot_standby = on
max_standby_streaming_delay = 30s
hot_standby_feedback = on
hot_standby_feedback这个参数值得多说一句。它让从库在执行查询时,会把自己的事务状态反馈给主库,避免主库VACUUM把从库还在用的旧版本元组清掉,导致从库查询报错canceling statement due to conflict with recovery。如果你从库上有长查询,强烈建议打开这个参数。
4.6 启动从库并验证
配置完成后,启动从库:
bash复制pg_ctl start -D /data/pg_slave
或者使用systemd:
bash复制systemctl start postgresql@pg_slave
启动后,在主库上执行:
sql复制SELECT client_addr, application_name, state, sync_state
FROM pg_stat_replication;
如果看到一条记录,state是streaming,说明从库已经成功连上并开始接收WAL流。如果sync_state是async,说明是异步复制;如果配置了同步复制,这里会显示sync。
再到从库上验证:
sql复制SELECT pg_is_in_recovery();
SELECT pg_last_wal_receive_lsn(), pg_last_wal_replay_lsn();
pg_is_in_recovery()返回true,说明这个实例确实在以备库模式运行。第二个查询的两个LSN如果一直在推进,说明WAL在持续接收并重放。
4.7 systemd托管实战
如果你用包管理器安装的PostgreSQL,一般自带systemd服务。但用pg_basebackup搭出来的从库,如果数据目录在自定义路径,可能需要单独配置服务。这里给出一个示例systemd unit:
ini复制[Unit]
Description=PostgreSQL 16 Slave
After=network.target
[Service]
User=postgres
Group=postgres
ExecStart=/usr/lib/postgresql/16/bin/postgres -D /data/pg_slave
ExecReload=/bin/kill -HUP $MAINPID
Restart=always
TimeoutSec=60
[Install]
WantedBy=multi-user.target
把unit文件放到/etc/systemd/system/postgresql-slave.service后:
bash复制systemctl daemon-reload
systemctl enable postgresql-slave
systemctl start postgresql-slave
这里有个细节:如果从库和主库在同一台机器上使用不同端口,还需要指定port参数。生产环境一般主从在不同机器,端口冲突问题不存在,但同一套配置换环境时很容易踩这个坑。
5. 主库挂了之后:从库提升与故障切换演练
从库搭好不是终点,故障切换才是检验成果的时刻。很多团队从库搭完就没碰过,真正主库宕机时手忙脚乱。这章讲清楚切换的原理和操作,以及最容易出错的地方。
5.1 什么是从库提升
从库提升(promote)就是把一个只读备库变成可读写的主库。物理层面做的事情是:停止WAL回放,生成一个新的时间线(timeline),开始接受写入。PostgreSQL 12以前用触发文件方式,12之后统一用pg_ctl promote命令。
5.2 提升前要确认的事项
在提升之前,要判断哪种情况需要提升:
- 架构上允许从库提升吗?如果有多个从库,是否要重新选择新的主库?
- 主库是永久故障还是暂时故障?如果只是网络抖动,主库本身没坏,强行提升会导致双主脑裂,后续再恢复会非常麻烦。
- 数据丢失容忍度是多高?异步复制下,主库崩溃时可能丢失最后几条事务。如果业务不能接受任何丢失,需要配置同步复制,这又是一个话题了。
5.3 提升操作
手动提升:
bash复制pg_ctl promote -D /data/pg_slave
如果用了systemd,可以直接:
bash复制systemctl stop postgresql-slave
# 移除复制配置后启动
更推荐的方式是用patroni这类高可用工具来管理提升。但即使有工具,你也得理解底层发生了什么。手动提升的核心步骤就一句话:执行pg_ctl promote后,确认实例已经可以接受写操作。
sql复制SELECT pg_is_in_recovery(); -- 执行前返回true,执行后返回false
SELECT timeline_id FROM pg_control_checkpoint(); -- 时间线应该增加了
5.4 故障切换后,老主库怎么办
这是我最想强调的场景。老主库可能只是短暂故障,恢复之后它还是一个"主库"状态的数据实例。如果你不处理,等同于集群里有两个主库,会发生脑裂。
正确的做法是把老主库重新降级为从库,挂到新主库下面。操作流程:
- 老主库恢复后,停止服务
- 清理老主库的数据目录(可以保留作为备份,但不能再当主库用)
- 用pg_basebackup重新从新主库拉一份全量备份,按从库方式配置起来
- 重新加入集群,继续流复制
这里有一个常见的错误做法:有些人觉得老主库数据"更全",想从老主库反向同步回新主库。这在大多数场景下非常危险,因为两个实例的时间线已经分叉,数据无法简单合并。
5.5 恢复演练清单
故障切换能力是要练出来的。我建议每个季度至少做一次完整的切换演练,流程固定下来:
- 在非业务低峰期,人为停止主库
- 观察监控告警是否正常触发
- 手动或自动提升从库
- 验证新主库读写正常
- 把原主库重新拉起来,降级为新从库
- 确认流复制恢复,数据延迟降为0
- 记录整个过程耗时,更新操作手册
演练过的团队和没演练过的团队,在面对真实故障时的心态和操作水准完全不一样。很多问题只有演练的时候才会暴露,比如连接串切换、应用发版后连接池失效、DNS解析缓存等。
6. 我在生产环境踩过的备份与从库相关的坑
最后分享几个真实场景里的坑。这些坑不是从文档里看来的,是真金白银的生产事故换来的。
6.1 备份目录权限不对,恢复时直接拒绝启动
有一次备份脚本用root用户执行pg_basebackup,输出目录的所有者是root。因为备份脚本在cron里跑了,没人注意到权限问题。后来要恢复,用postgres用户启动实例,结果报"data directory has invalid permissions"。
解决办法很简单但也容易被忽略:确保备份输出目录的所有者是postgres(或实际运行PostgreSQL的用户),权限是700。建议在备份脚本里加上chown,比如:
bash复制pg_basebackup ... -D /data/pg_backup/base_new
chown -R postgres:postgres /data/pg_backup/base_new
chmod 700 /data/pg_backup/base_new
6.2 WAL归档失败导致备份和从库同时掉链子
我遇到过一次很隐蔽的故障:archive_command配置了一个远程复制脚本,某天远程存储出了问题,归档连续失败。主库日志里开始刷archive_command失败的错误,但主库本身还正常。因为archive_mode=on时,主库会持续重试归档,一旦复制槽没有启用,WAL在pg_wal里堆积,磁盘使用率持续上涨。
最终因为磁盘满,主库进入只读模式,所有写业务直接中断。这次事故最痛苦的不是问题本身,而是从故障发生到告警触发花了一个多小时——因为磁盘使用率是缓慢上涨的,阈值没设置到足够敏感。
经验总结:归档失败必须立刻告警;pg_wal目录大小和磁盘使用率是核心监控指标,不能只看磁盘总量;archive_mode开启时,WAL的消费链路(归档或复制槽)必须保证至少一条是健康的。
6.3 复制槽把磁盘塞满
和上一条形成对照的是复制槽的另一面——从库永久移除后,如果对应的复制槽还留在主库上,主库会认为"这个从库还需要这些WAL",于是无限保留所有WAL。我有一次在清理测试环境时,删除了一个从库却忘记了清理复制槽,一周后主库报警磁盘空间不足,检查pg_replication_slots,发现一个inactive的slot躺着,pg_wal目录已经膨胀到几百GB。
监控建议:pg_replication_slots里active为false且持续超过一定时间的槽位,要自动告警。清理时用SELECT pg_drop_replication_slot('slot_name'),不要手动删文件。
6.4 从库查询慢,其实是在等待WAL回放
有一次业务反馈从库查询特别慢,检查了索引、统计信息都没问题。后来发现是主库上有大批量UPDATE操作,产生了大量WAL,从库的WAL回放一直跟不上,查询在执行时频繁和WAL回放冲突。
处理方式:
- 确认hot_standby_feedback已开启
- 查看pg_stat_database.conflicts里的冲突次数,判断冲突来源
- 如果经常冲突,适当调大max_standby_streaming_delay
- 更根本的解法是错峰执行大事务,或者考虑用逻辑复制隔离部分查询压力
6.5 时间线分叉引发的恢复迷惑
做了PITR(时间点恢复)的人都会遇到时间线概念。简单说,每次从库提升都会产生一个新的时间线,PostgreSQL用时间线来区分不同分支的历史。如果你要恢复到故障发生前的那一刻,恢复时除了指定目标时间点,还要注意是否需要指定target_timeline,否则可能恢复到错误的分支。
这里推荐一个习惯:每次做PITR恢复前,先查一下当前时间线和目标时间线,避免恢复完发现数据"不对"却不知道是时间线搞错了。
6.6 pg_basebackup在大库上的性能影响
很多人担心pg_basebackup会把主库拖垮。确实,大库备份时,主库的IO压力会明显上升,WAL发送也会占用带宽。但如果你用了流式传输(-Xs),并且从库或备份工具只拉取增量WAL,影响通常可控。
我习惯的做法:
- 备份尽量从从库拉,别只压在主库上
- 用nice或ionice降低备份进程的优先级
- 大库备份前评估主库的IO负载,避开业务高峰
- 如果跨机房备份,注意带宽占用,必要时对传输加密压缩
写在最后的一点建议
PostgreSQL物理备份和从库搭建,这两件事单看起来都不复杂,但要真正在生产环境里做到"靠得住",需要的是持续演练和细致的监控配置。我个人最深的体会是:备份不是用来安慰自己的,而是要能在关键时刻真的恢复回来。定期做恢复演练,把每次演练发现的问题记下来,补齐到自动化运维脚本里,比什么都重要。
如果你现在正准备搭建或优化PostgreSQL的备份和高可用,我建议从今天开始做好两件事:一是把pg_basebackup的全量备份和WAL归档跑通,二是把从库搭起来并完成一次完整的提升演练。这两件事做完,你的数据库应对突发故障的底气会完全不一样。
