PostgreSQL的逻辑复制自10版本正式可用以来,已经成为跨大版本升级、读写分离、异构同步场景下最常用的方案之一。与物理复制不同,逻辑复制是以表为单位进行变更传输的,订阅端可以只接收自己关心的数据。不过如果发布配置不当,订阅端依然会把发布库中所有被发布表的所有行都拉过来,白白消耗网络和磁盘。所以掌握逻辑复制的过滤策略,包括表级过滤、列级裁剪和行级过滤,是把逻辑复制用好的关键一步。

一、表级过滤:从发布对象入手控制同步范围
逻辑复制的过滤首先发生在发布端,也就是PUBLICATION的定义里。一个发布决定了哪些表、哪些操作类型(INSERT、UPDATE、DELETE、TRUNCATE)会被发送出去。最简单的做法是在创建发布时明确指定表清单:
-- 只发布两张表,且只同步插入和更新操作 CREATE PUBLICATION pub_orders FOR TABLE orders, order_items WITH (publish = 'insert, update');
这种方式适合同步范围固定、表数量不多的场景。如果希望后续发布库中新建的表自动纳入发布,可以使用FOR ALL TABLES,但这等于放弃了表级过滤,订阅端要准备好接收所有用户表的变更,风险较高,一般不建议在跨环境同步中使用。
还有一种折中方案是按数据库级别发布(FOR TABLES IN SCHEMA,13及以上版本支持),把某个schema下的全部表作为一个整体发布。它在管理上比逐表列举省事,又比FOR ALL TABLES收敛得多。需要注意,发布端对表的过滤是静态的,如果表被ALTER PUBLICATION ... DROP TABLE移出,订阅端已存在的数据不会自动删除,只是不再接收新的变更,清理工作要自己规划。
二、列级过滤:只发布需要的列
很多业务的宽表动辄几十列,其中可能包含敏感字段或订阅端根本用不到的大字段。从PostgreSQL 15开始,发布支持列级裁剪,创建发布时可以通过WITH子句配合ALTER PUBLICATION的SET COLUMNS来指定只发布部分列:
-- 发布orders表时只包含三列 ALTER PUBLICATION pub_orders SET TABLE orders (order_id, customer_id, amount);
被裁剪掉的列不会进入复制流,这既节省了带宽,也避免了敏感数据外泄。但要特别注意两个前提:一是发布的列集合中必须包含表的主键或副本标识(replica identity),否则UPDATE和DELETE无法在订阅端定位到目标行;二是订阅端的表结构必须与发布列匹配,列少了或类型不一致都会导致订阅报错中断。
如果表本身没有主键,可以为其设置副本标识,例如ALTER TABLE t REPLICA IDENTITY USING INDEX idx_unique,或者干脆FULL模式让整行参与匹配,但FULL会让复制流量明显变大,属于不得已的选择。列级过滤与行级过滤可以叠加使用,先用列裁剪缩小宽度,再用行过滤缩小范围,组合起来能大幅降低同步数据量。
三、行级过滤:用row filter实现精准的数据分发
行级过滤同样在发布端定义,PostgreSQL 15引入了row filter特性,允许对发布中的表附加WHERE条件,只有满足条件的行变更才会被发送:
-- 只同步北京地区的订单 ALTER PUBLICATION pub_orders SET TABLE orders (order_id, customer_id, amount) WHERE (region = 'beijing');
row filter的语义是“新行值匹配才发送”。对INSERT来说,插入的行满足条件即发送;对UPDATE来说,以更新后的新行判断,满足则发送(即使旧行不满足),不满足则不发送;DELETE则按删除前的行判断。这个语义与触发器或视图过滤的直觉略有差异,配置时一定要想清楚,尤其是UPDATE把一行从“满足”改成“不满足”时,订阅端并不会收到删除指令,容易造成两端数据漂移。
row filter还有几条硬性限制需要了解:表达式必须是不可变的(不能用now()这类易变函数);不能引用用户自定义函数或系统表;WHERE中引用的列必须都在发布列集合或副本标识中;同一个表在多个发布中如果被不同的订阅引用且过滤条件不同,要小心初始数据同步时各订阅各自按自己的条件拷贝,行为是独立的,不会互相干扰。排查行级过滤问题时,可以查询pg_publication_tables视图确认每张表实际生效的过滤条件:
SELECT pubname, schemaname, tablename,
pubinsert, pubupdate, pubdelete,
rowfilter, attrs
FROM pg_publication_tables
WHERE pubname = 'pub_orders';
四、订阅端与初始同步的配套注意事项
过滤策略虽然都定义在发布端,但订阅端的配置同样影响最终效果。创建订阅时如果不加WITH (copy_data = false),订阅会先做一次初始数据同步,这个同步同样遵循row filter和列过滤,也就是说订阅端拿到的初始快照就是过滤后的数据,这是符合预期的行为。但如果发布是在订阅创建之后才修改过滤条件的,已同步的存量数据不会回溯调整,需要手动清理或重建订阅。
另一个常见坑是冲突处理。由于过滤后的数据只是全量数据的一个子集,订阅端表上如果有本地写入,可能与复制过来的变更发生主键冲突,导致复制槽积压、WAL持续膨胀。建议订阅端的目标表保持只写来自复制的数据,或者通过Alter SUBSCRIPTION调整slot_name和逻辑,把冲突排查流程文档化。监控方面,重点盯三个指标:发布端复制槽的pg_replication_slots中的retained_wal、订阅端pg_stat_subscription的latest_end_time,以及两端数据量抽样比对,发现漂移及时修正。
总结来看,PostgreSQL逻辑复制的过滤策略分三个层次:表级靠发布对象清单控制广度,列级靠列裁剪控制宽度,行级靠row filter控制粒度。三者结合,再配合合理的副本标识和订阅端初始同步策略,就能搭建出一条既精准又轻量的数据同步链路,满足多租户分发、脱敏同步、灰度升级等多种业务需求。
PostgreSQL逻辑复制复制过滤修改时间:2026-09-01 21:04:40