广告位 · 页头(后台-模块管理-新建模块后替换此区域)

PostgreSQL逻辑复制中的行过滤策略如何平滑迁移?

在PostgreSQL逻辑复制环境中,发布端的行过滤条件并不是一个可有可无的附属配置。它直接控制着WAL中哪些行的变更会被解码并发送给订阅端,如果发布定义中的WHERE条件没有被完整迁移,即使表结构和订阅通道都恢复成功,目标库最终承载的数据范围也可能完全偏离预期。更麻烦的是,这类问题往往不会立即报错,而是在业务运行一段时间后以数据缺失、重复或主键冲突的形式暴露出来。因此,迁移逻辑复制链路时,需要把发布策略当作与表结构同等重要的一类对象来处理。

PostgreSQL逻辑复制中的行过滤策略如何平滑迁移?

下面从过滤策略的实际存储、常见迁移路径和验证回滚三个角度展开。

过滤策略存在哪里,怎么完整提取

PostgreSQL把发布、订阅以及发布中的表关系分别记录在几个系统目录里。发布对象本身在pg_publication,表与发布的关联在pg_publication_rel,行过滤表达式就保存在该表的prqual字段中。这个字段存储的是内部节点树,直接查询会得到一串不易阅读的文本,需要借助pg_get_expr函数把它还原成SQL条件。

在迁移之前,建议在源库执行以下查询,把每个发布中的schema、表名和行过滤条件一次性提取出来:

SELECT
    p.pubname AS pub_name,
    n.nspname AS schema_name,
    c.relname AS table_name,
    pg_get_expr(pr.prqual, pr.prrelid) AS row_filter,
    pr.prattrs AS column_list
FROM pg_publication_rel pr
JOIN pg_publication p ON p.oid = pr.prpubid
JOIN pg_class c ON c.oid = pr.prrelid
JOIN pg_namespace n ON n.oid = c.relnamespace
WHERE p.pubname NOT IN ('pglogical_origin')
ORDER BY p.pubname, n.nspname, c.relname;

这里需要注意,prqual为空表示该表是全表发布,prattrs为空表示发布所有列。如果源库同时使用了列过滤,那么迁移时列列表也必须一并迁移,否则订阅端表结构即使多出几列,也会因为发布端不发送这些列而出现NULL值或应用报错。很多迁移事故正是因为只搬了行过滤,没有搬列过滤,导致目标端查询结果不完整。

除了查询系统目录,还可以通过pg_dump观察发布DDL。在PostgreSQL 15及以上版本,发布对象会出现在数据库结构转储中,类似ALTER PUBLICATION ... ADD TABLE ... WHERE (...)的形式。不过pg_dump默认不导出订阅,且生成的发布语句可能分散在表定义之后,直接执行时需要先排除订阅相关依赖。因此更保险的做法是手工提取过滤条件,再由DBA在目标端统一重建。

迁移策略的两种落地方式

第一种方式是先在目标集群创建相同的表结构,然后根据源库提取的条件手动编写发布语句。假设源库有一个发布orders_pub,其中public.orders表只发布区域为华东且状态为已支付的行,迁移时在目标库执行:

CREATE PUBLICATION orders_pub FOR TABLE public.orders
WHERE (region = 'east' AND status = 'paid');

这种方式的优点是目标端对象少、链路干净,但需要人工保证每张表的过滤条件与源库完全一致。如果过滤条件里包含自定义函数、操作符或类型,必须先在目标库安装同样的扩展和函数,并且这些函数的稳定性级别不能被改变。例如源库过滤条件使用了is_valid_region(region)这样一个IMMUTABLE函数,目标库若把它改成STABLE,初始同步阶段处理历史数据的结果可能会不同。

第二种方式是让pg_dump生成结构脚本,再从中截取发布相关的部分。可以先执行pg_dump -h 源主机 -p 5432 -U postgres -d 源库 --schema-only --no-owner --no-privileges -f dump.sql,然后搜索CREATE PUBLICATION和ALTER PUBLICATION语句。这种方式适合发布数量多、表数量多的环境,但要注意pg_dump可能把发布语句放在接近文件末尾的位置,执行顺序上需要先建表、再建发布、最后恢复订阅。此外,如果源端发布使用了FOR TABLES IN SCHEMA语法,目标端schema名称不同就需要额外处理。

无论采用哪种方式,订阅都不能直接用pg_dump恢复。订阅是实例级对象,必须通过CREATE SUBSCRIPTION重新创建。创建订阅时,conninfo要指向源库的发布者,如果目标端已经手动导入了过滤后的历史数据,则建议加上WITH (copy_data = false),避免初始同步阶段再次拉取全表并覆盖现有数据。

迁移后的验证与回滚准备

迁移完成后,首先需要核对发布端的过滤表达式。可以在源库和目标库分别执行前面提到的系统目录查询,把结果导出成文本后进行比较。如果行数相同且每一行的表名、条件都一致,说明策略迁移没有遗漏。还可以使用pg_publication_tables视图快速查看某个发布包含哪些表以及是否带过滤条件,但该视图不直接展示表达式,详细核对还是需要pg_get_expr。

数据层面对比可以通过源库的过滤后行数与目标库实际行数来完成。以public.orders为例,源库发布端可以跑SELECT count(*) FROM public.orders WHERE (region = 'east' AND status = 'paid');,目标库订阅端直接跑SELECT count(*) FROM public.orders;,两边结果应当一致。如果目标库还接入了其他写入来源,则要按时间点或副本标识做更细粒度的差异分析,而不能只看总量。

监控阶段重点关注pg_stat_subscription视图。该视图能够显示每个订阅的最近接收LSN、最近写入时间以及是否出现错误。若发现订阅状态异常,可以先通过ALTER SUBSCRIPTION ... DISABLE暂停应用,再去检查发布端和订阅端的日志。回滚时,不建议直接删除源库的复制槽和发布对象,因为复制槽一旦删除,源库会清理对应的WAL,其他下游可能受影响。比较稳妥的做法是让新链路与旧链路并行运行一段时间,待业务验证通过后,再逐步停用旧订阅、删除旧发布。

最后还要提醒一个容易混淆的点:逻辑复制的行过滤只能配置在发布端,不能配置在订阅端。订阅端对表的约束、触发器和规则都可以继续发挥作用,但这些并不是复制过滤策略。如果迁移后在订阅端加了一堆触发器来拦截不需要的行,表面上数据范围符合预期,实际上复制链路仍然在传输全量WAL,订阅端本地再丢弃数据,既浪费网络和磁盘,也会让初始同步和冲突处理变得不可控。因此,行过滤条件必须原样迁移到发布端,才能保证整条复制链路的行为与源环境一致。

广告位 · 文章底部(后台-模块管理替换此区域)