PostgreSQL的逻辑复制基于发布订阅模型,发布端通过publication定义要同步的表和操作类型,订阅端通过subscription拉取数据。相比物理复制只能整库同步,逻辑复制最大的优势就是可以精细控制同步范围:哪些表要同步、哪些操作要复制、哪些行要传输,都可以通过过滤规则来决定。这篇文章就来详细讲解各个层面的过滤配置方法。

表级过滤:publication的创建与修改
表级过滤是逻辑复制中最基础的过滤方式,核心思路是在发布端定义publication时指定要发布的表。创建发布时可以列出具体表名,也可以用FOR ALL TABLES发布全库,但后者在实际生产中用得很少,因为pg_largeobject、系统表等对象的处理会带来额外负担。最常见的写法是这样:
-- 创建一个只包含两张表的发布,只同步 insert 和 update 操作
CREATE PUBLICATION pub_order FOR TABLE
orders, order_items
WITH (publish = 'insert, update');WITH子句里的publish参数支持insert、update、delete、truncate四种操作的组合,默认是全部开启。这个参数非常实用,比如单向归档场景只需要insert,把update和delete排除掉可以避免归档库的数据被源端删除操作影响。
发布创建之后还可以动态调整。用ALTER PUBLICATION可以随时增删表:
-- 向现有发布中追加表 ALTER PUBLICATION pub_order ADD TABLE customers; -- 从发布中移除表 ALTER PUBLICATION pub_order DROP TABLE order_items; -- 调整发布表集合(覆盖式设置) ALTER PUBLICATION pub_order SET TABLE orders, order_items, customers;
需要注意ADD TABLE和SET TABLE的区别:ADD是增量添加,SET是完全覆盖原有列表。另外,加入发布的表必须有主键或者非空的唯一索引(replica identity),否则update和delete操作在订阅端会报错,因为订阅端无法定位到具体要修改的行。可以提前用ALTER TABLE orders REPLICA IDENTITY FULL;把整行数据作为标识,但这个设置会增加WAL日志的体积,大宽表要谨慎使用。
如果想查看当前发布覆盖了哪些表,可以查询pg_publication_tables视图,排查同步问题时这个视图经常用到:
SELECT pubname, schemaname, tablename FROM pg_publication_tables WHERE pubname = 'pub_order';
行级过滤:ROW FILTER的配置与匹配逻辑
PostgreSQL 15引入了行级过滤功能,允许在publication中为每张表指定一个WHERE条件,只有满足条件的行才会被复制。这在多租户、数据分发的场景中特别有用,比如只想把华东地区的数据同步到异地节点。语法如下:
-- 创建带行级过滤的发布:只同步状态为已完成的订单
CREATE PUBLICATION pub_finished FOR TABLE orders
WHERE (status = 'completed');
-- 对已有发布追加行级过滤条件
ALTER PUBLICATION pub_order
SET TABLE orders WHERE (region = 'east');WHERE条件的表达式有一定的限制:只能引用当前表的列,不能使用子查询、聚合函数、不稳定函数(比如now()虽然可以用但强烈不推荐,会导致结果不确定),也不能引用其他表的数据。条件求值发生在发布端,也就是说不满足条件的行根本不会进入传输通道,能有效减少网络流量。
这里有一个非常关键的坑必须强调:行级过滤不会作用于初始数据同步。当你创建subscription并触发初始表拷贝时,COPY阶段会忽略WHERE条件,把全表数据都拷贝过去。官方文档明确说明了这一点。解决办法是先建表结构、手动灌入符合条件的数据,再创建订阅并跳过初始同步,或者干脆接受初始全量、在订阅端事后清理不符合条件的数据。
-- 跳过初始同步的订阅创建方式
CREATE SUBSCRIPTION sub_order
CONNECTION 'host=192.168.1.20 dbname=sourcedb user=repl'
PUBLICATION pub_order
WITH (copy_data = false);另外行级过滤还有第二个坑:如果一张表在同一个publication里被指定了多次(不同WHERE条件),或者该表同时存在于多个被同一订阅引用的publication中,多个条件之间是OR关系,也就是任意一个条件满足就会被复制。如果表在发布中出现了但没有WHERE子句,等价于同步全表。理解这个合并规则对排查“为什么有些不该同步的行出现了”这类问题很重要。
列级过滤与订阅端的控制手段
PostgreSQL 18之前的版本,逻辑复制无法过滤列,UPDATE产生的日志会包含所有列的旧值和新值(replica identity full的情况下)。如果想隐藏敏感列,通常的做法是在发布端建视图或者触发器中转。实际项目中更常见的方案是在中间加一层缓冲表,只把需要外发的列写进去再发布,安全性更好。
订阅端同样有控制同步范围的手段。可以在订阅中指定publish参数的对应行为,也可以通过配置文件过滤。不过更实用的做法是结合pg_hba.conf和网络层控制:订阅端的连接用户在发布端要有SELECT权限和REPLICATION权限,通过权限收敛来限制它能拿到什么数据,本质上也是一种过滤。发布端建议单独创建复制账号并按表授权:
-- 发布端创建专用复制用户并只授权需要的表 CREATE USER repl_user REPLICATION LOGIN PASSWORD 'xxx'; GRANT SELECT ON orders, order_items TO repl_user; GRANT USAGE ON SCHEMA public TO repl_user;
订阅端还有一个常用的排查参数pg_stat_subscription视图,配合查看worker进程的工作状态。如果怀疑过滤规则没生效,可以在发布端查pg_replication_slots确认槽位的活跃状态,再看pg_stat_replication中sent_lsn和confirmed_flush_lsn是否推进。两边的LSN对得上但数据没到,多半就是被过滤条件拦下了,这时可以直接在发布端执行WHERE条件手动验证某行是否应该被同步。
常见问题与运维注意事项
逻辑复制的过滤规则虽然灵活,但有几个容易被忽视的问题。第一,DDL完全不会复制,你在发布端给表加了一列,订阅端不会自动加,同步会直接报错中断。规范的做法是用工具(比如Flyway或Liquibase)在两端同时执行DDL变更,变更完成后再修改publication。第二,序列不会复制,订阅端自增主键可能冲突,需要在订阅端调整序列起始值。
第三,被过滤掉的DELETE和UPDATE依然会在发布端产生WAL解析开销,只是不传输。所以行级过滤不是性能优化的万能药,如果表写入量极大但同步比例很低,更好的方案是分区后只发布目标分区。第四,修改publication的表集合后,订阅端只有在刷新订阅之后才会感知变化:
-- 订阅端刷新发布定义(不会触发重新拷贝) ALTER SUBSCRIPTION sub_order REFRESH PUBLICATION; -- 刷新并重新拷贝新增表的数据 ALTER SUBSCRIPTION sub_order REFRESH PUBLICATION WITH (copy_data = true);
最后建议在生产环境为逻辑复制槽配置监控。复制槽会阻止WAL被清理,如果订阅端长期宕机,发布端的磁盘可能被WAL撑爆,这是逻辑复制最经典的运维事故。可以设置max_slot_wal_keep_size作为兜底保护,同时配合告警监控pg_replication_slots中confirmed_flush_lsn的滞后情况,把风险控制在可控范围内。
总结一下,PostgreSQL逻辑复制的过滤体系分三个层次:publication的表级过滤决定同步哪些表,publish参数决定哪些操作类型,行级WHERE条件决定哪些行。三层组合起来基本能覆盖绝大多数数据分发需求,再配合订阅端的权限控制和初始同步策略,就能搭建出一套精准可控的同步链路。
PostgreSQL逻辑复制publication复制过滤规则修改时间:2026-09-16 01:14:38