PostgreSQL 的逻辑复制以发布和订阅模型为基础,将数据库中的更改以流式方式传递到下游。与物理复制直接复制数据块不同,逻辑复制允许按表、按行、按列做筛选,这为多租户隔离、数据脱敏、跨库聚合等场景提供了便利。设计过滤策略时,需要理解发布端行过滤、列过滤以及订阅端参数如何协同工作,否则容易出现初始数据不一致、增量更新冲突或复制槽积压。

发布端行过滤的执行边界
行过滤通过在发布定义中的 WHERE 子句实现。例如只把 orders 表中属于华南区域的数据同步到订阅端,可以创建如下发布:
CREATE PUBLICATION pub_orders FOR TABLE orders WHERE (region = 'cn-south');
这个过滤条件会在逻辑解码阶段生效,只有满足 region = 'cn-south' 的 INSERT、UPDATE、DELETE 才会被写入复制流。但需要注意,PostgreSQL 对行过滤条件有严格限制:表达式必须是不可变的(immutable),不能包含易变函数如 random()、now() 或用户自定义的 stable/volatile 函数。如果尝试使用 now(),创建发布时会直接报错,因为逻辑解码无法保证在不同时间点得到一致的过滤结果。
更关键的是,行过滤不会影响初始表同步阶段。当订阅端以 copy_data = true 方式创建订阅时,初始同步会复制表的全部现有行,而不是只复制满足过滤条件的行。这会导致订阅端在首次同步后包含大量本应被过滤掉的数据,而后续增量更改又不会更新这些行,造成数据长期滞留。解决办法通常是在订阅端先手动清理不符合条件的行,或者使用分区表将过滤逻辑下沉到表结构,再只发布相应分区。
行过滤还要求表具备可用的复制标识。如果表没有主键也没有唯一索引,逻辑复制会拒绝增量更新,因为无法定位要修改的旧行。实践中建议为每个参与逻辑复制的表设置主键,或者使用 ALTER TABLE ... REPLICA IDENTITY FULL,但 FULL 模式会显著增加 WAL 日志量,只适合小表或低频更新场景。
列过滤如何影响复制标识与初始同步
列过滤通过发布定义中的列列表来实现,它控制哪些列的值会出现在逻辑复制流中。例如只同步用户表的 id、email 和 status 三列:
CREATE PUBLICATION pub_users FOR TABLE users (id, email, status);
与行过滤不同,列过滤在初始同步阶段就会生效。订阅端表如果存在其他列,那些未被发布的列在初始 COPY 过程中会保持 NULL 或默认值。因此,订阅端表结构可以与发布端不完全一致,但必须包含被发布的列,并且这些列的类型要兼容或可转换。这为数据脱敏和最小化传输提供了很大的灵活性,但也会带来复制标识方面的约束。
逻辑复制的 UPDATE 和 DELETE 操作需要旧行副本,而旧行副本的内容由复制标识决定。如果表使用主键作为复制标识,那么主键列必须包含在列列表中,否则订阅端无法根据主键找到要更新的行,apply worker 会报错并停止。例如一个用户表主键是 user_id,但发布时只列出了 email 和 status,那么当订阅端收到 UPDATE 时,旧行中缺少 user_id,无法匹配目标行。此时要么把主键列加入列列表,要么将复制标识改为 FULL,让 PostgreSQL 使用整行旧值来定位,但这样又会扩大日志体积。
列过滤还会影响 UPDATE 语义。如果被过滤的列恰好发生了变化,发布端不会发送该变更,订阅端对该列保持旧值。这本身符合最小同步原则,但一旦后续需要重新开启该列,就需要刷新发布并重新同步数据。实践中应提前规划哪些列是敏感的或不需要复制的,避免中途频繁调整。
订阅端参数与策略流的协同配置
创建订阅时,copy_data、streaming 和 binary 等参数会直接影响过滤策略的执行方式。copy_data 控制是否在初始阶段复制现有数据,如果设为 false,则只从订阅创建后的增量更改开始同步,这适合已经通过其他方式完成了数据迁移的场景。streaming 参数决定是否在事务提交前就开始流式传输更改,对于大事务,开启 streaming = parallel 可以降低订阅端延迟,但会消耗更多内存和网络资源。
CREATE SUBSCRIPTION sub_orders CONNECTION 'host=10.0.0.5 dbname=source user=replicator password=secret' PUBLICATION pub_orders WITH (copy_data = false, streaming = parallel, synchronous_commit = off);
当发布端同时存在行过滤和列过滤时,订阅端 apply worker 会先解析逻辑解码输出,再根据发布表的列列表和行过滤条件进行二次校验。虽然行过滤在解码阶段已经生效,但订阅端仍然会检查复制流中的每一行是否满足当前发布定义,以防止发布定义在复制过程中被修改导致的竞态条件。这种双端校验保证了数据流的最终一致性,但也意味着订阅端的 pg_subscription_rel 状态必须与发布端保持同步。
在日常运维中,可以通过视图 pg_publication_tables 查看某个发布实际生效的过滤策略:
SELECT pubname,
schemaname,
tablename,
attrs,
rowfilter
FROM pg_publication_tables
WHERE pubname = 'pub_orders';
其中 attrs 列显示列列表,rowfilter 列显示行过滤表达式。如果看到 attrs 为空表示发布所有列,rowfilter 为空表示没有行过滤。这个视图是排查过滤策略是否按预期生效的第一入口。
变更过滤策略后的同步修复与排查
在生产环境中,修改已有发布的过滤条件或列列表并不会自动同步到订阅端。例如在发布端执行 ALTER PUBLICATION pub_orders SET TABLE orders WHERE (region = 'cn-north'),已有的订阅不会感知这个变化,除非在订阅端执行 ALTER SUBSCRIPTION sub_orders REFRESH PUBLICATION。刷新操作会重新关联发布中的表,对于新增的表会触发一次全量同步,对于已有表则只更新内部元数据,不会重新复制现有数据。因此如果行过滤条件变得更严格,订阅端中原本多出的行依然需要手动删除。
如果过滤条件频繁变化,建议将过滤逻辑设计成稳定的分区键或固定枚举值,避免使用会随时间变化的表达式。例如不要使用 status = 'active' 这种容易变化的过滤,而是使用 tenant_id = 'tenant_a' 这种稳定的隔离键。这样每个订阅可以对应一个固定的租户或业务域,过滤策略一旦建立就不会频繁调整。
故障排查时,订阅端 pg_stat_subscription 视图可以显示当前 apply worker 的状态和错误信息。常见错误包括:logical replication target relation ... is missing replicated column 表示列过滤与订阅端表结构不匹配;publisher did not send replica identity column expected by the logical replication target relation 表示复制标识列被过滤掉了。遇到这些错误时,应先在发布端检查 pg_publication_tables,再确认订阅端表结构和复制标识设置,最后执行 ALTER SUBSCRIPTION ... REFRESH PUBLICATION 重新同步元数据。
PostgreSQL逻辑复制复制过滤发布订阅修改时间:2026-10-04 14:14:06