PostgreSQL 逻辑复制通过发布和订阅模型实现表级甚至行级的数据同步。发布端可以定义行过滤条件和列过滤列表,只有满足条件的行变更才会被写入复制流,未发布的列也不会参与传输。过滤策略的调优并不只是写一个 WHERE 子句那么简单,它涉及执行计划、WAL 解码成本、复制槽保留、订阅端冲突等多个维度。下面从几个关键角度拆解。

一、发布端行过滤和列过滤的执行机制
在 PostgreSQL 15 之前,逻辑复制只能整表发布,想要只同步部分数据需要借助触发器或额外中间表。PG 15 开始发布支持 WHERE 子句,例如:
CREATE PUBLICATION pub_active_users FOR TABLE users WHERE (status = 'active');
该发布只会把 users 表中 status 等于 active 的行的 INSERT、UPDATE、DELETE 发送给订阅端。列过滤可以这样定义:
CREATE PUBLICATION pub_users_cols FOR TABLE users (id, name, last_login);
这里只发布 id、name、last_login 三个列。理解这些语法之后,还需要清楚过滤到底发生在哪一端。行过滤是在发布端逻辑解码阶段完成的,而不是订阅端应用阶段。逻辑解码会读取 WAL 中表的变更记录,将每行数据转换为逻辑消息,在转换过程中应用 WHERE 条件,只有条件为真的行才会生成复制消息。因此过滤条件越复杂,每一行都要付出表达式计算成本。如果该表变更量非常大,复杂条件可能成为解码瓶颈。
列过滤则直接影响发送的数据量,未发布列的变化不会产生列值复制,但行级变更仍然会触发,因为至少需要主键或 REPLICA IDENTITY 来定位目标行。如果没有主键且未设置 REPLICA IDENTITY FULL,逻辑复制会报错或无法更新删除。所以列过滤时一定要保证订阅端能唯一匹配行。
WHERE 条件在行过滤中并不是通过索引执行,因为解码时不是普通 SQL 执行计划,而是逐行评估表达式。表达式本身是否能利用索引不关键,但要注意如果 WHERE 条件引用的列经常被更新,可能导致 WAL 中该列的旧值和新值都需要解码,增加开销。应该尽量选择稳定、选择性高的字段作为过滤条件,比如租户 ID、地区、状态等。对于过滤后仍然较大的数据量,考虑使用分区表,将发布指向特定分区而不是依赖行过滤,这样解码时可以直接跳过非目标分区的 WAL 记录,性能更好。
二、解码内存与复制槽相关的参数调优
logical_decoding_work_mem 是逻辑解码过程中用于缓存事务变更的内存上限。默认值通常为 64MB。如果发布端有大量事务或过滤条件导致行需要临时排序时,该值过小会触发磁盘落盘,解码速度下降。适当增大该参数,例如在 postgresql.conf 中设置 logical_decoding_work_mem = 256MB,可以让更多变更在内存中处理。但注意该参数是针对每个逻辑解码进程的,多个订阅会各自分配内存,需要结合服务器总内存确定。可以使用以下 SQL 查看当前值和修改:
SHOW logical_decoding_work_mem; ALTER SYSTEM SET logical_decoding_work_mem = '256MB'; SELECT pg_reload_conf();
复制槽是逻辑复制的核心组件。每个订阅在发布端都有一个对应的复制槽,发布端会根据复制槽的 confirmed_flush_lsn 保留 WAL 文件。如果订阅端长时间断开或消费缓慢,WAL 会不断堆积,严重时占满源库磁盘。过滤策略如果设计得过于宽泛,大量行变更进入复制流,订阅端应用不过来也会导致复制槽延迟。需要定期检查 pg_replication_slots 和 pg_stat_subscription:
SELECT slot_name, active, pg_wal_lsn_diff(pg_current_wal_lsn(), confirmed_flush_lsn) AS lag_bytes FROM pg_replication_slots WHERE slot_type = 'logical'; SELECT subname, received_lsn, latest_end_lsn, last_msg_send_time, last_msg_receipt_time FROM pg_stat_subscription;
如果 lag_bytes 持续增长,需要首先确认订阅端是否正常,而不是盲目加大过滤或参数。过滤策略调优更倾向于减少发布端发送量,但复制槽膨胀本质是消费能力不足或网络问题。
其他参数如 max_sync_workers_per_subscription 控制初始同步阶段表同步的并行度。如果订阅涉及多张表且每张表的过滤条件不同,多个 worker 可以并行执行初始 COPY。但每个 worker 都会在源库上执行查询并占用连接,调大该值可能加剧源库负载。建议从默认值 2 开始,观察源库 CPU 和 I/O 后再逐步增加。另外,wal_sender_timeout 和 wal_receiver_timeout 对网络抖动敏感的环境有用,但不要设置过小,否则频繁重连会导致复制槽状态反复切换,影响过滤逻辑重新初始化。
三、过滤策略变更与订阅端数据处理
修改发布的行过滤条件或列列表后,已有的增量数据会立即按新条件发送,但订阅端已有的历史数据不会自动删除或补齐。例如原来同步 status = active 的行,后来改成 status IN ('active','pending'),那么之前 pending 状态的行不会自动补发,订阅端会缺少这部分数据。如果需要补齐,可以手动在订阅端执行一次 COPY 或使用逻辑备份恢复,或者重新初始化订阅。重新初始化会先 TRUNCATE 目标表再全量同步,适合数据量不大的情况。对于大表,建议使用工具对比数据差异并定向补数。
过滤策略还影响冲突处理。逻辑复制默认使用 REPLICA IDENTITY 来定位目标行,如果目标是 UPDATE 或 DELETE,而订阅端没有对应行,会产生冲突并停止应用。过滤条件可能会放大冲突概率,比如过滤条件只同步部分行,但订阅端表上还有历史数据或由其他应用写入的行,主键冲突就可能发生。解决思路:明确表的写入责任,确保订阅端目标表只由逻辑复制写入;对于无法避免冲突的场景,可以设置 REPLICA IDENTITY FULL,让 UPDATE 使用整行旧值定位,但会显著增大 WAL 日志量。也可以使用 ALTER SUBSCRIPTION ... SKIP (lsn) 跳过冲突事务,但这只是临时手段,需要后续处理数据差异。
实践对比:假设 orders 表每天新增 200 万行,只需要同步 region = 'north' 的数据,占比约 10%。如果使用行过滤,发布端仍然要解码全部 200 万行并计算 region 条件,只是发送 20 万行。如果改为按 region 分区,将 north 分区单独发布,则解码阶段可以直接跳过其他分区的 WAL,性能可提升一个数量级。对于非分区表且过滤比例较高(比如超过 50%),行过滤收益有限,不如直接整表同步或重新设计表结构。列过滤则适合大宽表只同步少量核心字段的情况,能明显减少网络和订阅端磁盘占用。
四、监控过滤效果与常见诊断路径
判断过滤策略是否真正生效,不能只看发布定义。可以先在发布端确认发布详情,例如使用 \dRp+ pubname 在 psql 中查看 WHERE 条件和列列表。或者查询系统表:
SELECT p.pubname, t.tablename, pt.attrs, pt.qual FROM pg_publication p JOIN pg_publication_tables pt ON p.oid = pt.pubname JOIN pg_tables t ON t.tablename = pt.tablename WHERE p.pubname = 'pub_active_users';
其中 qual 列会显示行过滤表达式,attrs 显示发布的列位置。需要确认订阅端使用的是最新发布定义,修改发布后可以执行 ALTER SUBSCRIPTION sub REFRESH PUBLICATION; 来刷新元数据。如果是 PG 15 以下版本,则不支持行过滤,需要升级或使用触发器方案。
监控过滤效果还可以观察发布端 WAL 发送量。pg_stat_replication 中的 write_lag、flush_lag、replay_lag 反映订阅端消费延迟;pg_replication_slots 中 spill_count 和 spill_txns 可以反映逻辑解码落盘情况。如果 spill_count 很高,说明 logical_decoding_work_mem 不足或者过滤条件导致排序溢出。另外,可以通过 EXPLAIN 分析过滤表达式在普通 SQL 下的执行成本,虽然逻辑解码不走查询计划,但表达式复杂度基本一致,可以作为参考。如果 EXPLAIN 显示表达式需要全表扫描且耗时高,那么在解码中同样会拖慢速度。
常见诊断路径总结:先确认复制链路是否健康,再检查发布过滤定义,然后分析解码参数和复制槽膨胀情况,最后根据过滤比例评估是否改用分区或其他方案。调优顺序上,优先优化过滤条件本身,例如移除不必要的函数调用、避免使用易变的函数如 now() 或 random(),因为这些会影响逻辑解码的一致性和性能。其次再调整内存和并行参数,最后才考虑重新设计发布架构。这样可以在影响最小的前提下获得稳定收益。

