PostgreSQL的逻辑复制(Logical Replication)自10版本正式引入以来,已经成为跨库同步、读写分离、异地容灾等场景的主流选择。它基于发布订阅模型:发布端创建PUBLICATION指定要复制的表和操作,订阅端创建SUBSCRIPTION拉取WAL中的逻辑变更。但真实业务里很少出现“整库照搬”的需求,绝大多数同步链路都需要过滤——只同步部分表、部分行、部分操作,甚至只同步特定的数据库来源。过滤规则写在哪一层、怎么分布,直接影响链路的性能、可维护性和数据一致性。这篇文章就来把PostgreSQL逻辑复制中各个层级的过滤能力梳理清楚。

发布端过滤:publication是过滤的主战场
发布端的过滤是最接近数据源头的一层,也是逻辑复制中最核心的过滤手段。它的基本单位是PUBLICATION对象,通过FOR TABLE子句指定要发布的表,通过FOR ALL TABLES表示全库发布,通过WAL级别的publish参数控制要发布哪些操作类型。
最基础的表级过滤写法如下,只发布指定的三张表,并且只同步insert和update,不同步delete:
-- 创建发布,只包含指定表和指定操作
CREATE PUBLICATION sales_pub
FOR TABLE orders, order_items, customers
WITH (publish = 'insert, update');
从PostgreSQL 15开始,发布端还支持行级过滤(Row Filter)和列级过滤(Column List),这是过滤能力的一次大升级。行级过滤通过WHERE子句实现,只有满足条件的行变更才会被发送给订阅端,能显著减少网络传输量:
-- 行级过滤:只发布华东区域的订单 ALTER PUBLICATION sales_pub ADD TABLE orders (WHERE (region = 'east')); -- 列级过滤:customers表只发布部分列,隐藏手机号等敏感字段 ALTER PUBLICATION sales_pub ADD TABLE customers (id, name, city);
行级过滤有几个容易踩坑的地方需要特别注意。第一,初始数据同步阶段(COPY阶段)同样会应用行级过滤条件,也就是说历史数据迁移时也只会搬运满足条件的行,这个行为和很多同学的预期一致,但如果你在过滤条件里用了不可IMMUTABLE的函数,比如依赖当前时间的条件,发布端和订阅端执行结果可能不一致,PostgreSQL会直接禁止这类表达式。第二,行级过滤条件中的UPDATE有一个特殊规则:如果一行更新前不满足条件但更新后满足了,订阅端会收到一个INSERT;反之则收到一个DELETE。这意味着订阅端的数据不一定是发布端某张表的完整快照,设计下游逻辑时必须意识到这一点。
另外,发布端的publish参数可以细化为insert、update、delete、truncate四种操作的任意组合。典型用法是单向归档场景只发布insert,避免订阅端的误删操作被回传。这个参数在16版本后进一步拆分成了publish和publish_via_partition_root,后者控制分区表的发布以根表还是子分区为单位,做分区表同步时建议开启,可以避免订阅端必须建立完全相同的分区结构。
订阅端过滤:subscription层面的控制手段
虽然发布端承担了大部分过滤职责,但订阅端也有自己独立的过滤维度,而且有些需求只能在订阅端实现。首先是create_subscription时的单一发布限制——一个SUBSCRIPTION可以订阅多个PUBLICATION,但要求所有发布对同一张表的定义必须兼容,这本身就是一种隐式约束。
订阅端最重要的过滤参数是copy_data和origin。copy_data控制初始同步时是否搬运存量数据,设为false就只同步订阅创建之后的增量变更,适合下游已经有历史数据的场景:
-- 创建订阅:跳过存量数据,只消费增量
CREATE SUBSCRIPTION sales_sub
CONNECTION 'host=192.168.1.10 port=5432 dbname=sales user=repl'
PUBLICATION sales_pub
WITH (copy_data = false, streaming = 'parallel');
origin参数是PostgreSQL 16引入的,用来防止复制环路。它的核心问题是:订阅端应用变更时,这些变更自身也会产生WAL,如果这个库同时还是另一个链路的发布端,变更就会被循环传播。origin有三种取值:any表示不过滤(默认,适合最末端节点)、local表示只复制本地产生的变更、none表示只复制没有任何源标记的变更。在双向同步或环形拓扑中,正确设置origin是避免数据风暴的关键。
订阅端还可以配合ALTER SUBSCRIPTION ... SET TABLE SKIP或者更常用的逻辑复制冲突处理机制来做事后过滤。当订阅端发生唯一键冲突时,默认整个apply worker会报错停止,你可以通过设置disable_on_error让订阅自动暂停,然后人工介入决定这条数据要不要。这虽然不是主动过滤,但在实际运维中是兜底手段之一。
网络与权限层过滤:pg_hba.conf和角色控制
过滤不只是数据层面的概念,访问控制层的过滤同样属于策略分布的一部分,而且往往被忽视。逻辑复制的连接走的是普通libpq连接,但要求连接角色有REPLICATION权限或SUPERUSER,并且pg_hba.conf里必须有匹配的条目。最推荐的做法是使用专门的复制数据库,把复制流量和业务流量隔离开:
-- pg_hba.conf中限制复制来源IP -- host replication repl 192.168.1.0/24 scram-sha-256 -- 创建专门的复制角色,最小权限原则 CREATE ROLE repl WITH REPLICATION LOGIN PASSWORD 'StrongPass';
从PostgreSQL 15开始还引入了pg_create_subscription预定义角色,允许非超级用户创建订阅,权限粒度进一步细化。在生产环境中,发布端只开放复制库的pg_hba.conf条目给订阅端IP段,配合防火墙规则做双重过滤,即使订阅端被攻破也无法触达业务库。
值得一提的是,发布端如果只创建了publication但没给订阅角色授权表的SELECT,初始同步的COPY阶段会直接失败,而增量阶段的REPLICA IDENTITY读取也需要相应权限。所以权限过滤本质上和数据过滤是绑定的,做策略分布时要一起规划。
策略分布的实践原则与层级选择
把前面几个层面汇总一下,可以得出一张清晰的对照表:
| 过滤层级 | 过滤粒度 | 典型手段 | 适用场景 |
|---|---|---|---|
| 发布端publication | 库、表、行、列、操作类型 | FOR TABLE、WHERE、列清单、publish参数 | 绝大多数业务过滤需求 |
| 订阅端subscription | 存量数据、变更来源 | copy_data、origin | 增量接入、防环路 |
| 访问控制层 | 连接、库、角色 | pg_hba.conf、REPLICATION权限 | 安全隔离、最小权限 |
实际选型时有两条经验值得遵循。第一,过滤尽量前移,能在发布端解决的就不要留给订阅端,因为发布端过滤直接减少WAL解码和网络传输的开销,对发布库的性能影响最小;订阅端能做的过滤(比如origin)本质上是在数据已经传输过来之后再丢弃,白白浪费了带宽和解码成本。第二,表级以上的粗粒度过滤放发布端,来源级别的过滤放订阅端,两者职责不重叠。比如多租户系统里,租户A的库发布部分表给分析库,租户过滤条件写在发布端的WHERE里;而分析库如果同时接收多个上游,用origin区分变更来源防止互相污染。
最后提一个常见的误区:有人会在发布端和订阅端写相互矛盾的过滤条件,比如发布端只发insert,订阅端却期待完整数据来维护物化视图。逻辑复制没有事务级回滚补发机制,一旦某条变更被任何一层过滤掉,订阅端就永久缺失了它。所以设计过滤策略时,先在纸上画出数据流经的每一层,确认每条规则“过滤掉的数据下游确实不需要”,再动手建PUBLICATION,能省去大量排查数据不一致的时间。合理的过滤策略分布,本质上是在性能、安全性和数据完整性之间找平衡点,把每条规则放在它执行成本最低、语义最清晰的那一层。
PostgreSQL逻辑复制publication修改时间:2026-09-03 20:35:20