PostgreSQL 复制与备份策略
数据是命,丢了就完。本文讲清 PostgreSQL 的高可用与灾备:流复制(主从)、同步 vs 异步、物理备份(pg_basebackup/WAL 归档)、逻辑复制、以及一套"3-2-1 备份"策略和恢复演练,让你的数据库扛得住故障。
本文要点
- 主题聚焦:DevOps
- 适合人群:独立开发者、全栈工程师、AI 应用开发者
- 核心价值:快速理解背景、实践路径、常见误区和工程建议
PostgreSQL 复制与备份策略:数据是命,备份是命根
我见过太多团队的故事是这样的:业务跑得好好的,某天夜里主库磁盘炸了,运维一查——备份上周就没成功过,因为磁盘满了脚本静默失败;或者某个新来的实习生在生产库 DELETE FROM users WHERE id = 123; 漏了 WHERE,一个回车下去,全表没了。等 DBA 赶到现场,只能面对一堆哭丧着脸的产品经理。
数据库宕机只是"难受",数据丢了才是"完蛋"。硬件会坏(SSD 有写入寿命、磁道会老化、机房会断电、网线会被保洁阿姨拔了),人会犯错(误删库、误删表、误改数据、跑了不该跑的迁移脚本),还有勒索软件、地震、洪水。高可用(HA)解决的是"还能用",灾备(DR)解决的是"还能找回数据",两者不能互相替代。
PostgreSQL 在复制和备份这块生态已经相当成熟。这篇文章我把实战中最关键的几条线一次讲透:流复制、同步/异步的取舍、物理备份与 PITR、逻辑复制、3-2-1 策略,以及最容易被忽略、却最致命的一环——恢复演练。
一、流复制:主库的每一次改动,从库都跟得上
PostgreSQL 的复制底座是 WAL(Write-Ahead Logging,预写式日志)。任何事务在修改数据页之前,先把改动写到 WAL 文件里。流复制(Streaming Replication)做的事就是:主库把这些 WAL 流式推给从库,从库重放(replay)WAL,从而实现近乎实时的物理同步。
这是物理复制——按字节复制,从库和主库长得一模一样,连内部数据页布局都一致。优点是通用、高效、对应用透明;缺点是从库只能是整个集群级别的复制,不能挑表。
一个典型的主从架构:
┌──────────────┐ WAL stream ┌──────────────┐
│ Primary │ ─────────────▶ │ Standby │
│ (读写主库) │ │ (只读从库) │
└──────────────┘ └──────────────┘
▲ │
│ 应用写入 │ 只读查询 / 故障切换
│ ▼
┌───┴───┐ 热备/报表
| 客户端 | 连接过来读
└───────┘
主库的关键配置(postgresql.conf):
wal_level = replica # 至少 replica,逻辑复制要 logical
max_wal_senders = 10 # 允许几个从库连过来
wal_keep_size = 1024 # MB,为从库保留的 WAL,避免追不上
hot_standby = on # 从库在恢复期间是否可读(配在从库上)
archive_mode = on # 开启归档,见后文备份章节
archive_command = 'test ! -f /backup/wal/%f && cp %p /backup/wal/%f'
主库还要建一个有复制权限的角色:
CREATE ROLE replicator WITH REPLICATION LOGIN PASSWORD 'a-strong-secret';
并在 pg_hba.conf 里放行复制连接:
host replication replicator 10.0.0.0/24 scram-sha-256
从库的初始化,用 pg_basebackup 从主库拉一份全量:
pg_basebackup \
-h primary.host -U replicator -D /var/lib/postgresql/data \
-Fp -Xs -P -R
-R 会自动写一个 standby.signal 文件并配上 primary 连接信息,从库起来后就进入持续恢复模式,源源不断接收 WAL。
Failover(故障切换):主库挂了,把某个从库提升为新主库。PostgreSQL 10+ 用 pg_promote():
SELECT pg_promote(); -- 在从库上执行,几秒内变成可写主库
生产环境别手动敲,用 Patroni / repmgr / pg_auto_failover 这类工具来托管选主、VIP/DNS 切换、脑裂防护。手撸 failover 脚本是新人最爱踩的坑——脑裂(split-brain)两个主库同时写,数据直接冲突烂掉。
二、同步 vs 异步:一致性和性能的单选题
流复制默认是异步的:主库写完 WAL、本地落盘就告诉客户端"提交成功",不等从库确认。这意味着:
- 写延迟低,主库性能不受从库拖累;
- 但如果主库刚提交完就炸了,而那部分 WAL 还没发到从库,这部分数据就丢了。
同步复制(synchronous_commit = on + synchronous_standby_names 配置)则是:主库必须等到至少一个同步从库确认收到 WAL 后,才向客户端返回成功。
synchronous_standby_names = 'FIRST 1 (standby1, standby2)'
好处是强一致——主库 commit 成功的事务,从库一定有;坏处是写延迟变高,而且同步从库一旦全挂,主库会 hang 住(为了保护一致性,写入会卡住直到超时或从库恢复)。所以同步复制通常配 FIRST 1 (a, b),至少留一个备份候选。
| 维度 | 异步复制 | 同步复制 |
|---|---|---|
| 一致性 | 最终一致 | 强一致 |
| 写性能 | 快 | 慢(多一个 RTT) |
| 丢数据风险 | 可能丢少量最新事务 | 几乎不丢 |
| 主库可用性 | 不受从库影响 | 同步从库全挂则写阻塞 |
怎么选? 我的实战经验:
- 金融、交易、账本类:核心表走同步,丢一笔钱就是事故。
- 内容、日志、行为流:异步足矣,丢最后几秒日志业务能接受,别为它牺牲性能。
- 混合打法:PostgreSQL 支持按事务级别开关同步,关键操作临时
SET LOCAL synchronous_commit = on;,日常异步跑得飞快。
记住一句话:同步复制保的是 RPO(数据丢失量),牺牲的是 RTO(恢复时间)和性能,没有银弹。
三、物理备份:全量 + WAL 归档,能穿越回任意时间点
复制解决的是"现在还在跑",备份解决的是"过去能找回"。PostgreSQL 的物理备份组合拳是 pg_basebackup 全量 + WAL 归档增量,合起来能做 PITR(Point-in-Time Recovery,时间点恢复)——把数据库恢复到过去任意一秒。
全量基础备份 持续归档的 WAL
pg_basebackup + archive_command = PITR
(某个时刻的快照) (之后所有改动) (恢复到任意时间点)
第一步:全量基础备份。在线热备,不锁库:
pg_basebackup -D /backup/base -Ft -z -P -X fetch
# -Ft 打成 tar,-z 压缩,-X fetch 备份期间需要的 WAL 也带上
或者用 pgBackRest / Barman 这类专业工具,带并行、压缩、增量、远端管理,生产环境强烈推荐,别用裸 pg_basebackup 自己拼脚本。
第二步:持续 WAL 归档。archive_command 把主库产生的每个 WAL 段拷到独立存储(对象存储、NFS、异地):
archive_command = 'aws s3 cp %p s3://my-pg-wal/%f'
WAL 文件名是单调递增的,所以归档天然有序。
第三步:做 PITR 恢复。假设周三下午 3 点 17 分有人误删了表,现在要恢复到 3 点 16 分:
- 把最近一次全量备份解压到新数据目录;
- 在数据目录放
recovery.signal; - 配
postgresql.auto.conf:
restore_command = 'aws s3 cp s3://my-pg-wal/%f %p'
recovery_target_time = '2026-07-17 15:16:00+08'
recovery_target_action = 'promote'
- 启动 PostgreSQL,它会回放所有归档 WAL,到目标时间停下并 promote 成新主库。
这个能力是物理备份的核心价值:不仅能恢复到"最近",还能精准恢复到"事故前一秒"。逻辑备份做不到这点。
四、逻辑备份与逻辑复制:灵活但不强求一致
逻辑备份用 pg_dump / pg_dumpall,导出的是 SQL 语句或自定义格式,跨版本、跨架构、能挑表挑 schema。适合迁移、升级前快照、把生产数据导一份到测试库。
pg_dump -Fc -d mydb -f mydb.dump # 自定义压缩格式,可并行恢复
pg_dump -d mydb -t users -t orders -f sub.dump # 只导部分表
pg_restore -j 4 -d newdb mydb.dump # 4 线程并行恢复
逻辑备份的硬伤:不能 PITR,只能恢复到 dump 那一刻;大库 dump 一次可能几小时,期间数据还在变。所以逻辑备份是补充,不能替代物理备份 + 归档。
逻辑复制(Logical Replication) 是另一回事,它工作在表级别,基于发布/订阅模型:主库 CREATE PUBLICATION 发布一组表的变更,目标库 CREATE SUBSCRIPTION 订阅。变更以逻辑格式(INSERT/UPDATE/DELETE)传过去,目标库重放。
-- 发布端(主库)
CREATE PUBLICATION pub_users FOR TABLE users, orders;
-- 订阅端(另一个库,可以是不同 PG 实例)
CREATE SUBSCRIPTION sub_users
CONNECTION 'host=primary.host user=rep password=...'
PUBLICATION pub_users;
逻辑复制的杀手级场景:
- 跨大版本升级:PG 13 → PG 16,逻辑复制几乎是零停机升级的标准方案;
- 选择性复制:只把
users表同步到分析库,不拖整个集群; - 异构分发:把数据拆到不同库给不同业务线;
- 合并多库到数仓/数据湖。
代价是:逻辑复制不传 schema(表结构要先建好)、不支持序列、大事务有延迟、对主键强依赖(没主键的表 UPDATE/DELETE 会炸)。
五、3-2-1 备份策略:别把鸡蛋放一个篮子里
业界公认的备份铁律叫 3-2-1:
- 3 份副本(生产数据 + 至少 2 份备份);
- 2 种不同介质(比如对象存储 + 磁带,或云盘 + 本地 NAS);
- 1 份完全离线/异地(防机房级灾难:火灾、洪水、区域性宕机)。
映射到 PostgreSQL 的实战配置:
┌─────────────────────────┐
│ 生产 PG 主库(副本 1) │
└──────────┬──────────────┘
│ 流复制
┌────────▼────────┐
│ 异地只读从库 │ (副本 2,不同可用区)
└────────┬────────┘
│ pg_basebackup + WAL 归档
┌─────────────┼──────────────┐
▼ ▼ ▼
对象存储 S3 本地 NAS 离线冷备
(介质 A) (介质 B) (异地/介质 B)
关键原则:
- 备份必须自动化,脚本跑在 cron / k8s CronJob 里,跑完发指标到监控;
- 备份产物要校验完整性(
pg_verifybackup校验 basebackup),别等恢复时才发现文件损坏; - WAL 归档和全量备份必须分目录/分 bucket,防止全量备份覆盖掉归档;
- 加密:静态加密 + 传输加密,备份里是全量数据,泄露即等同于数据库泄露;
- 测试恢复,这一步见下一节,比备份本身还重要。
保留策略:遵循 GFS(Grandfather-Father-Son)——每日备份保留 7 天,每周备份保留 4 周,每月备份保留 12 个月。既控制成本,又能应对"上周发现的误删"这种延迟发现的事故。
六、恢复演练:没演练过的备份,等于没有
这一节字数最少,却最重要。我见过 90% 的"备份失效"事故,都是因为从来没真正恢复过。
常见翻车场景:
- 备份脚本天天跑,但目标盘满了,
cp静默失败,没人发现; - WAL 归档链有缺口(某段时间
archive_command报错了),PITR 卡住; - 备份加密了,但密钥存在同一台挂掉的机器上;
- 恢复流程文档过时,真实命令和新版本不兼容;
- 团队没人会做 PITR,真出事时手忙脚乱,RTO 拉到几小时。
对策:每月一次恢复演练,把它当 生产事故 来对待。
演练清单:
- 定期测恢复:在隔离环境,从最新备份拉起一个完整 PG,跑业务只读校验(行数、关键表 checksum、抽样比对);
- 测 PITR:挑一个过去的时间点,看能不能精确恢复到那一秒;
- 测 failover:主库
kill -9,看 Patroni/repmgr 几秒切到从库,应用连接池是否自动重连; - 记录指标:每次演练记下 RTO(恢复耗时,目标比如 15 分钟)和 RPO(丢数据量,目标比如 0 或秒级);
- 复盘文档:演练里踩的坑写进 runbook,下次真出事照着做。
把这套做成 CI 流水线的一部分更好——每次备份成功后自动在临时环境跑恢复校验,失败立刻告警。备份是承诺,恢复是兑现,只有兑现过的承诺才算数。
小结
- 复制和备份是两件事:复制保"在线",备份保"可回滚",HA 不能替代 DR。
- 流复制是物理复制的底座:WAL 流到从库,近乎实时,但要靠 Patroni 这类工具托管 failover,别手撸。
- 同步 vs 异步按业务选:金融账本走同步保 RPO,日志内容走异步保性能,关键事务还能临时切同步。
- 物理备份 + WAL 归档 = PITR:能恢复到任意时间点,这是逻辑备份做不到的,生产必配。
- 3-2-1 + 恢复演练:3 份副本、2 种介质、1 份异地;每月测一次真恢复,没演练过的备份等于没有。
金句:"数据库的可靠性,不在于你做了几份备份,而在于你能在多久之内把数据完好地恢复回来——并证明它真的能。" 备份是写下来的承诺,恢复是兑现的那一刻。把每一次演练都当成事故预演,当真正的故障在凌晨四点敲门时,你才有底气说:数据没事,接着睡。
Related
相关推荐
文章
Docker 部署 Next.js 社区项目的注意事项
围绕 DevOps、Next.js 的技术趋势、工程实践和独立开发视角。
文章
OpenTelemetry 如何帮助你观察 AI 应用?
围绕 DevOps、AI 的技术趋势、工程实践和独立开发视角。
文章
长运行工具调用:Operation、Poll 与 Cancel 的终态协议
初始请求超时后,工具可能仍在服务端运行;发出取消也不等于副作用已停止。本文为单次长运行工具调用建立 operation 身份、轮询水位、取消竞态和唯一终态接受点。
文章
Agent 记忆删除传播:从用户遗忘请求到可验证清除
删除主表一行不会清掉向量、摘要、缓存和恢复后的备份。本文用删除状态机、代际墓碑、派生血缘与端到端 canary 验证真正遗忘。