1 PostgreSQL 中的物理复制与逻辑复制:机制、差异与选型
本文从 WAL(预写式日志) 出发,说明 物理复制(通常即流复制 / streaming replication) 与 逻辑复制(logical replication) 各自如何产生、传输与应用变更,并标出常见误区与版本相关行为。阅读后你应能回答:谁在解码 WAL、副本上跑的是什么、能过滤什么、DDL 与冲突怎么处理。
1.1 共同基础:WAL 是什么
- WAL(Write-Ahead Log):事务提交前,先把足够的信息持久化到日志,崩溃后可重放以恢复一致性。
- 概念分层:WAL 是「存储与恢复」层面的日志;其上可叠加 物理重放(按页/低层记录恢复)或 逻辑解码(把变更解释为对行的插入/更新/删除等)。
准确性:不同大版本在 WAL 内部记录格式、可选特性(如压缩、某些子系统的记录类型)上会演进;下文描述的是 PostgreSQL 主流架构思路,具体对象名以你使用的版本文档为准。
1.2 物理复制(Physical Replication)一般指什么
在 PostgreSQL 社区语境里,物理复制几乎总是指:备库按与主库相同的物理存储布局重放 WAL,典型实现是 流复制(streaming replication)(持续把 WAL 段流式传给备库),辅以 pg_basebackup 等做初始基线。
1.2.1 工作方式(心智模型)
- 主库生成 WAL;备库是「整个数据目录」级别的镜像演进(同一集群、同一套 catalog 与数据文件语义)。
- 备库在恢复模式下应用 WAL,通常表现为 热备(hot standby):可读,默认不可写(除非 promoted 或可写副本等架构)。
1.2.2 你能得到什么
- 强一致副本形态:与主库在存储层「同构」,适合 HA 故障切换、只读负载分担、备份窗口卸载。
- 整库复制:一个 primary 对应 standby 时,是实例级复制,不是「只订阅某几张表」为第一公民模型(表级过滤属于逻辑层思路)。
1.2.3 同步策略(与「物理/逻辑」正交但常一起考)
- 异步:提交不必等备库刷盘;延迟存在。
- 同步:
synchronous_standby_names等配置下,满足条件的同步副本确认前事务可阻塞提交(细节与remote_apply/on/quorum等级别有关,以官方文档为准)。
1.2.4 典型组件与名词
- WAL sender / WAL receiver:流式传输端与接收端进程。
- 复制槽(replication slot):物理槽用于保留 WAL,防止主库过早回收导致备库追不上(需监控槽滞后与磁盘)。
pg_basebackup:获取基线数据目录 + 启动流或配合归档的初始同步。
1.3 逻辑复制(Logical Replication)是什么
逻辑复制不追求「字节级同一数据文件」,而是:从 WAL 中解码出逻辑变更(对某表的 INSERT/UPDATE/DELETE),再通过复制协议在订阅端应用。
1.3.1 工作方式(心智模型)
- 发布端(publisher):定义 publication(发布哪些表;新版本可带行/列过滤等能力,见下文版本提示)。
- 订阅端(subscriber):定义 subscription,连到上游拉取变更并应用。
- 解码依赖 逻辑解码(logical decoding) 与输出插件;内置路径常用 pgoutput(PostgreSQL 原生协议插件)。
1.3.2 你能得到什么
- 部分复制:可选择表集合;适合 多租户拆分、汇聚、跨版本迁移(在支持矩阵内)、异构下游 等。
- 副本可写:订阅库上未被订阅覆盖的表可本地读写;与物理备库默认只读不同。
- 版本边界更灵活:逻辑复制在官方支持矩阵内可跨一定大版本(以发行说明为准),而物理流复制通常要求主备同大版本(升级通过逻辑复制或 pg_upgrade 等另议)。
1.3.3 关键概念:Replica Identity
逻辑复制要把 UPDATE/DELETE 变成「可定位旧行」的流,需要 replica identity:
- DEFAULT:有主键则用主键列;有唯一且非部分索引则可用其列集(细节见文档);否则解码 UPDATE/DELETE 可能受限。
- FULL:用整行作为键;日志与带宽开销更大,但适合无主键表(仍要评估性能)。
- NOTHING:不记录旧行键信息,无法合理传播 UPDATE/DELETE(对这类表逻辑复制通常不适用)。
1.3.4 DDL 与限制(高频考点)
- 逻辑复制以表数据变更为主:DDL 不会自动像 DML 一样复制(
CREATE TABLE、改列类型等需另行策略:手工、事件触发器、外部工具或新版本特性)。 - 序列(sequence):历史行为里
SERIAL/IDENTITY的序列值 需要专门理解(常见做法是显式同步序列或在切换时setval);具体以版本文档「Logical Replication」章节为准。 - 大对象(large objects):通常不在逻辑复制默认路径内。
- 冲突:订阅端若对同一主键已有不同行,应用变更可能失败;需监控与重试策略。
1.4 对照表:物理 vs 逻辑(抓住本质)
| 维度 | 物理复制(流复制) | 逻辑复制 |
|---|---|---|
| 解码位置与内容 | 备库按 WAL 物理恢复 | 发布端解码为逻辑行变更 |
| 复制粒度 | 实例 / 数据目录级 | 表级 publication(可子集) |
| 备库形态 | 典型为热备,默认只读 | 订阅库可混合读写(非复制表) |
| DDL | 随 WAL 隐含一致(同一 catalog) | 不自动复制 DDL(需额外流程) |
| 典型用途 | HA、只读扩展、灾备 | 部分同步、汇聚、滚动升级辅助 |
| 版本关系 | 主备通常同大版本 | 在支持矩阵内可跨大版本(需验证) |
表为教学归纳;生产约束以官方矩阵与实测为准。
1.5 复制槽:物理槽与逻辑槽不要混谈
- 物理复制槽:为 standby 保留 WAL,避免被
CHECKPOINT/回收机制删掉导致断流。 - 逻辑复制槽:为 逻辑解码消费位点 保留 WAL;长期不消费会膨胀 WAL 与磁盘。
两者都叫 slot,但消费者与监控指标不同;运维上要分别建告警。
1.6 监控与排障:两边都问的三个问题
- 滞后(lag):物理看
pg_stat_replication的write/flush/replay_lag;逻辑看订阅端pg_stat_subscription_stats等(字段随版本演进)。 - WAL 堆积原因:网络、磁盘、单线程应用、大事务、解码慢、冲突停写等。
- 一致性语义:同步提交级别、
remote_apply与「读己之写」在只读副本上的行为,以文档为准,不要凭直觉写进 SLA。
1.7 版本提示(阅读官方 Release Notes 时重点搜)
- PostgreSQL 10:逻辑复制进入内核主线的重要版本节点(此前更多依赖外部逻辑解码生态)。
- PostgreSQL 15+:逻辑发布订阅能力持续增强(例如行过滤、列列表等方向——以你所用确切小版本的 Release Notes / Current 文档 为准,不在此绑定具体语法以免与你环境不一致)。
准确性原则:涉及 CREATE SUBSCRIPTION ... WITH (...) 的参数默认值、并行应用 worker、两阶段提交与逻辑复制交互等,请以 当前大版本官方文档 为唯一权威。
1.8 选型建议(工程向)
- 首要目标是机架级 HA 与只读扩展:默认先设计 物理流复制 + 监控 + 切换。
- 需要跨库汇聚、表级同步、订阅端本地扩展表、或在支持矩阵内做滚动升级辅助:评估 逻辑复制,并接受 DDL 流程化与冲突治理 成本。
- 二者可并存:例如 HA 仍走物理副本,分析侧用逻辑订阅拉部分表(注意上游 WAL 与解码负载)。
1.9 常见误解纠正
- 误解:「逻辑复制更先进,应替代物理复制。」正解:目标不同;HA 机架级切换仍以物理副本常见且路径成熟。
- 误解:「逻辑复制等于行级 CDC。」正解:形态相近,但 PostgreSQL 逻辑复制是内置订阅模型,不等价于所有 Debezium/Kafka 生态语义;对接外部系统常仍用 逻辑解码插件 + 导出格式。
- 误解:「有主键就一定能逻辑复制 UPDATE/DELETE。」正解:还要 publication 包含该表、权限、
replica identity配置、以及订阅端约束与冲突处理等。
1.10 建议实验(加深理解)
- 起两个本地实例(或同实例两库),建立 publication/subscription,观察
pg_replication_slots与pg_stat_replication/ 订阅统计视图。 - 故意在订阅端制造主键冲突,观察应用错误与 WAL 滞留。
- 对比
pg_basebackup+ 流复制 初始搭建与 逻辑订阅 初始数据同步(copy_data等选项以文档为准)在时间与锁上的差异。
1.11 参考入口(权威)
- 官方手册章节:High Availability, Load Balancing, and Replication;Logical Replication;Warm Standby;Replication Progress Monitoring。
- 以你部署的 确切 PostgreSQL 版本号 阅读对应版本的 HTML/PDF 文档,避免混读导致参数默认值错误。
1.12 文档说明
本文为概念与运维向梳理,不包含可直接执行的生产级配置片段,以免与你的版本默认值偏离;落地时请粘贴你环境的 SHOW server_version; 并对照该版本文档逐条校验。