下面给你一份基于 PG18 的“逻辑复制支持系统表同步”完整设计文档(可直接用于研发/评审)。我会严格结合你前面分析的源码链路来设计,重点保证:
- 不破坏 PostgreSQL 现有逻辑复制架构
- 可控扩展(白名单机制)
- 具备产品化落地能力
一、背景与目标
1.1 背景
在 PostgreSQL 18 中:
逻辑复制默认仅支持:
- 普通表(heap table)
- 分区表
- sequence
系统表(catalog)被严格排除
排除路径包括:
- WAL 层:
RelationIsLogicallyLogged()排除 catalog - publication 层:
is_publishable_relation()排除 catalog - pgoutput 层:过滤不可发布 relation
- 订阅端:仅允许普通表作为 apply 目标
1.2 问题
当前机制导致:
无法同步:
- schema 元信息
- 用户自定义 catalog-like 表
- 扩展系统表
无法实现:
- DDL 自动复制(你前面设计的核心目标)
- metadata replication
- cluster-level config sync
1.3 目标
实现:
✅ 支持指定系统表通过逻辑复制同步到订阅端
并满足:
- 不影响现有逻辑复制
- 不默认开启(必须显式控制)
- 不破坏 catalog 一致性
- 支持未来 DDL replication 扩展
二、总体设计
2.1 核心思路
采用:
“系统表白名单 + 分层放开”机制
而不是:
❌ 全量放开 system catalog
2.2 架构扩展点
WAL生成
↓
Logical Decoding
↓
pgoutput(发布端)
↓
Logical Replication Protocol
↓
apply worker(订阅端)
改造点:
| 层 | 是否修改 | 说明 |
|---|---|---|
| WAL/logical logging | ✅ | 允许系统表产生 logical 信息 |
| decoding | ⚠️ | 复用现有 |
| pgoutput | ✅ | 放行系统表 |
| protocol | ❌ | 不改 |
| apply | ✅ | 支持系统表目标 |
三、核心设计
3.1 系统表白名单机制(核心)
新增统一判断函数:
bool IsLogicalRepSystemCatalog(Oid relid);
实现方式(第一版)
static bool
IsLogicalRepSystemCatalog(Oid relid)
{
switch (relid)
{
case MyCatalogOid:
case MySecondCatalogOid:
return true;
default:
return false;
}
}
3.2 GUC 支持(建议)
新增参数:
logical_replication_system_catalogs = 'pg_my_catalog,pg_xxx'
解析为 OID list。
四、发布端设计
4.1 修改 WAL 逻辑
文件:
src/include/utils/rel.h
修改点
原逻辑:
#define RelationIsLogicallyLogged(relation) \
(!IsCatalogRelation(relation) && ...)
修改为:
#define RelationIsLogicallyLogged(relation) \
((!IsCatalogRelation(relation) || \
IsLogicalRepSystemCatalog(RelationGetRelid(relation))) && ...)
4.2 publication 层改造
文件:
src/backend/catalog/pg_publication.c
4.2.1 check_publication_add_relation
原:
if (IsCatalogRelation(rel))
ERROR
改:
if (IsCatalogRelation(rel) &&
!IsLogicalRepSystemCatalog(RelationGetRelid(rel)))
ERROR
4.2.2 is_publishable_class
原:
!IsCatalogRelationOid(relid)
改:
(!IsCatalogRelationOid(relid) ||
IsLogicalRepSystemCatalog(relid))
4.2.3 FirstNormalObjectId 限制
原:
relid >= FirstNormalObjectId
问题:
- system table OID < FirstNormalObjectId
改:
(relid >= FirstNormalObjectId ||
IsLogicalRepSystemCatalog(relid))
4.3 schema publication 限制
禁止:
FOR TABLES IN SCHEMA pg_catalog
原因:
- 风险太高
五、pgoutput 层设计
文件:
src/backend/replication/pgoutput/pgoutput.c
5.1 放行系统表
无需直接改 pgoutput_change:
if (!is_publishable_relation(relation))
return;
因为我们已经修改:
is_publishable_relation()
5.2 限制能力(必须)
对于系统表:
禁用:
- row filter
- column list
- generated columns
在:
get_rel_sync_entry()
中增加:
if (IsLogicalRepSystemCatalog(relid))
{
entry->rowfilter = NULL;
entry->columns = NULL;
}
5.3 协议不修改
继续使用:
- RELATION message
- INSERT/UPDATE/DELETE
六、订阅端设计
6.1 relation mapping
文件:
src/backend/replication/logical/relation.c
修改 logicalrep_rel_open
允许:
system catalog relation
6.2 apply 限制
文件:
execReplication.c
修改 CheckSubscriptionRelkind
原:
only RELATION / PARTITIONED / SEQUENCE
改:
if (IsCatalogRelation(localrel) &&
!IsLogicalRepSystemCatalog(RelationGetRelid(localrel)))
ERROR;
6.3 写入路径
继续使用:
- heap_insert
- heap_update
- heap_delete
⚠️ 限制
必须要求:
| 条件 | 必须 |
|---|---|
| 有 replica identity | ✅ |
| 有唯一键 | ✅ |
| 不涉及 shared catalog | ✅ |
七、功能限制(必须)
7.1 不支持
| 功能 | 状态 |
|---|---|
| shared catalog | ❌ |
| initial copy | ❌ |
| DDL replication | ❌ |
| schema 自动包含 | ❌ |
| TRUNCATE | ⚠️ |
7.2 推荐支持对象
仅支持:
- 用户扩展 catalog 表
- metadata 表(类似 pglogical)
八、风险分析
8.1 最大风险
1. catalog 语义破坏
系统表不仅是数据:
- 依赖关系
- cache invalidation
- OID 分配
2. apply 副作用缺失
普通 DML 不触发:
- catalog rebuild
- dependency update
3. shared catalog
跨数据库共享:
- 逻辑复制无法正确处理
九、测试设计
9.1 基本测试
- INSERT / UPDATE / DELETE
- 多事务
- 并发
9.2 边界测试
- rollback
- crash recovery
- slot 重放
9.3 兼容性测试
- 非系统表不受影响
- publication 行为不变
十、未来扩展
10.1 DDL replication
该机制可直接支撑:
- CREATE TABLE
- ALTER TABLE
- DROP
(通过系统表变化传播)
10.2 pg_publication_sync 结合
你前面设计的:
- pg_publication_sync 表
- LogLogicalDDLMessage
可以复用本机制:
👉 统一 DDL + catalog replication
十一、总结
核心改动一句话总结
在 PG18 中,通过“白名单方式”打通 WAL → publication → pgoutput → apply 四层,让指定系统表具备逻辑复制能力。
最关键的4个改动点
RelationIsLogicallyLogged(WAL层)is_publishable_relation(publication层)pgoutput_change(发送层)CheckSubscriptionRelkind(apply层)
推荐落地策略
👉 不要做:
- 全量 system table 复制
👉 应该做:
- 只支持 1~2 张系统表
- 或:
- 设计专用 catalog 表(推荐)
实现步骤
下面给你一版 基于 PG18、接近可编译的补丁级设计方案。这版不是“完整可直接提交社区”的 patch,而是 面向你当前原型开发 的最小闭环实现:只允许白名单系统表参与逻辑复制,不支持 shared catalog、schema 级自动包含、initial copy、row filter、column list。现有 PG18 源码里,系统表之所以被挡住,确实主要在这几处:RelationIsLogicallyLogged() 排除 catalog;check_publication_add_relation() 和 is_publishable_class() 排除 system table / 低 OID 对象;pgoutput_change() 再次按 is_publishable_relation() 过滤;订阅端 CheckSubscriptionRelkind() 只接受普通表、分区表和 sequence;官方文档也明确写了“Only regular tables may be the target of replication.”。(GitHub)
一、目标与边界
1.1 目标
实现一个最小可运行版本:
- 指定白名单系统表可加入 publication
- 发布端能把该系统表的 DML 变更写入逻辑复制流
- 订阅端能把该系统表作为 apply 目标表
- 不影响普通逻辑复制
1.2 明确不做
第一版不做这些,否则复杂度会急剧上升:
- shared catalog
FOR TABLES IN SCHEMA pg_catalog- initial copy
- row filter
- column list
- DDL 同步
- 任意系统表全开放
这几个限制是必要的,因为 PG18 当前 publication / subscriber 设计就是面向普通表,尤其订阅端文档和代码都明确把目标对象限定在 regular table 语义附近。(GitHub)
二、实现策略
核心思路只有一句话:
不要改成“系统表都可复制”,而是增加一个“逻辑复制系统表白名单”机制。
建议新增一个统一 helper:
bool IsLogicalRepSystemRelationOid(Oid relid);
bool IsLogicalRepSystemRelation(Relation rel);
第一版直接写死白名单,后续再扩成 GUC 或 catalog 参数。
三、建议新增文件
建议新增一个公共头文件和一个实现文件,避免把白名单逻辑散落在各处。
3.1 新增头文件
文件:
src/include/replication/logicalsysrel.h
内容建议:
#ifndef LOGICALSYSREL_H
#define LOGICALSYSREL_H
#include "postgres.h"
#include "utils/rel.h"
extern bool IsLogicalRepSystemRelationOid(Oid relid);
extern bool IsLogicalRepSystemRelation(Relation rel);
#endif
3.2 新增实现文件
文件:
src/backend/replication/logical/logicalsysrel.c
第一版实现:
#include "postgres.h"
#include "catalog/pg_class_d.h"
#include "replication/logicalsysrel.h"
#include "utils/rel.h"
bool
IsLogicalRepSystemRelationOid(Oid relid)
{
/*
* 第一版:只允许指定白名单系统表。
* 这里先写死,后续可替换成 GUC / catalog 配置。
*/
switch (relid)
{
/* 示例:替换为你的目标系统表 OID */
/* case MySystemCatalogRelationId: */
/* return true; */
default:
return false;
}
}
bool
IsLogicalRepSystemRelation(Relation rel)
{
return rel != NULL &&
IsCatalogRelation(rel) &&
IsLogicalRepSystemRelationOid(RelationGetRelid(rel));
}
还要把它加进对应 Makefile:
文件:
src/backend/replication/logical/Makefile
加入:
OBJS += logicalsysrel.o
四、补丁一:放开 WAL/logical logging 对白名单系统表的限制
PG18 里 RelationIsLogicallyLogged(relation) 明确排除了 system table;而 RelationIsAccessibleInLogicalDecoding(relation) 只是为了让 decoding snapshot 能访问 catalog 历史状态,并不等于会把它作为复制数据发布出去。(GitHub)
4.1 修改文件
src/include/utils/rel.h
4.2 现状
当前宏等价于:
#define RelationIsLogicallyLogged(relation) \
(XLogLogicalInfoActive() && \
RelationNeedsWAL(relation) && \
(relation)->rd_rel->relkind != RELKIND_FOREIGN_TABLE && \
!IsCatalogRelation(relation))
这正是系统表在底层被排除的关键之一。(GitHub)
4.3 修改建议
先包含新头文件:
#include "replication/logicalsysrel.h"
然后改成:
#define RelationIsLogicallyLogged(relation) \
(XLogLogicalInfoActive() && \
RelationNeedsWAL(relation) && \
(relation)->rd_rel->relkind != RELKIND_FOREIGN_TABLE && \
(!IsCatalogRelation(relation) || IsLogicalRepSystemRelation(relation)))
4.4 作用
这样只有白名单系统表才会进入“需要记录足够逻辑信息以供 WAL 提取 tuple change”的路径,其他系统表保持 PG18 原行为不变。(GitHub)
五、补丁二:publication 层允许白名单系统表加入
PG18 里 check_publication_add_relation() 会直接拒绝 system table,而 is_publishable_class() 又同时用 IsCatalogRelationOid(relid) 和 relid >= FirstNormalObjectId 两个条件排除系统表。后者很重要:很多系统表即使你绕过 catalog 检查,也仍会因为 OID 太小被拦下。(Doxygen PostgreSQL)
5.1 修改文件
src/backend/catalog/pg_publication.c
5.2 增加 include
#include "replication/logicalsysrel.h"
5.3 修改 check_publication_add_relation()
原逻辑片段:
/* Can't be system table */
if (IsCatalogRelation(targetrel))
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg(errormsg, RelationGetRelationName(targetrel)),
errdetail("This operation is not supported for system tables.")));
改成:
/* Can't be system table unless explicitly allowed */
if (IsCatalogRelation(targetrel) &&
!IsLogicalRepSystemRelation(targetrel))
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg(errormsg, RelationGetRelationName(targetrel)),
errdetail("This operation is not supported for system tables.")));
5.4 修改 is_publishable_class()
原逻辑:
static bool
is_publishable_class(Oid relid, Form_pg_class reltuple)
{
return (reltuple->relkind == RELKIND_RELATION ||
reltuple->relkind == RELKIND_PARTITIONED_TABLE ||
reltuple->relkind == RELKIND_SEQUENCE) &&
!IsCatalogRelationOid(relid) &&
reltuple->relpersistence == RELPERSISTENCE_PERMANENT &&
relid >= FirstNormalObjectId;
}
改成:
static bool
is_publishable_class(Oid relid, Form_pg_class reltuple)
{
bool is_whitelisted_sysrel = IsLogicalRepSystemRelationOid(relid);
return (reltuple->relkind == RELKIND_RELATION ||
reltuple->relkind == RELKIND_PARTITIONED_TABLE ||
reltuple->relkind == RELKIND_SEQUENCE) &&
(!IsCatalogRelationOid(relid) || is_whitelisted_sysrel) &&
reltuple->relpersistence == RELPERSISTENCE_PERMANENT &&
(relid >= FirstNormalObjectId || is_whitelisted_sysrel);
}
5.5 为什么两处都要改
因为 check_publication_add_relation() 负责 SQL/API 入口,而 is_publishable_class() / is_publishable_relation() 负责 publication 资格判断的底层统一逻辑。只改其中一处都不够。PG18 源码注释里还专门提到了 FirstNormalObjectId 这层限制及其历史问题。(Doxygen PostgreSQL)
六、补丁三:禁止通过 schema publication 引入系统表
PG18 里 check_publication_add_schema() 会拒绝 system schema / toast schema。第一版建议保留,不要放开 pg_catalog schema 级发布。(Doxygen PostgreSQL)
6.1 处理建议
不改:
check_publication_add_schema()
这样第一版只允许:
ALTER PUBLICATION pub ADD TABLE pg_catalog.xxx;
不允许:
CREATE PUBLICATION pub FOR TABLES IN SCHEMA pg_catalog;
6.2 原因
这是为了防止把整套 catalog 无意间纳入 publication,导致 apply 端失控。PG18 当前对 system schema 的拒绝是明确存在的。(Doxygen PostgreSQL)
七、补丁四:pgoutput 层放行,但限制高级特性
pgoutput_change() 和 TRUNCATE 路径都直接调用 is_publishable_relation(relation);只要上一步已经放开白名单系统表,这里大概率不需要改主判断。(Doxygen PostgreSQL)
7.1 修改文件
src/backend/replication/pgoutput/pgoutput.c
7.2 增加 include
#include "replication/logicalsysrel.h"
7.3 不改 pgoutput_change() 主入口
保留:
if (!is_publishable_relation(relation))
return;
因为 publication 层已经接管了“谁可发布”的判断。pgoutput_change() 当前正是这么依赖 is_publishable_relation() 的。(Doxygen PostgreSQL)
7.4 修改 get_rel_sync_entry(),禁用 row filter / column list
第一版建议给系统表加保护:
伪代码如下:
static RelationSyncEntry *
get_rel_sync_entry(PGOutputData *data, Relation relation)
{
RelationSyncEntry *entry;
Oid relid = RelationGetRelid(relation);
entry = ... existing code ...;
if (IsLogicalRepSystemRelationOid(relid))
{
/*
* 第一版不支持对白名单系统表使用:
* - row filter
* - column list
* - generated columns 特殊发布控制
*
* 如果 publication 元数据中配置了这些能力,直接报错。
*/
if (entry->publish_as_relid != relid)
ereport(ERROR,
(errmsg("system relation \"%s\" does not support publish_via_partition_root",
RelationGetRelationName(relation))));
if (entry->exprstate != NULL)
ereport(ERROR,
(errmsg("system relation \"%s\" does not support row filters",
RelationGetRelationName(relation))));
if (entry->columns != NULL)
ereport(ERROR,
(errmsg("system relation \"%s\" does not support column lists",
RelationGetRelationName(relation))));
}
return entry;
}
7.5 为什么要这么做
因为 get_rel_sync_entry() 是 pgoutput 为 relation 构建发布描述、过滤器、列映射缓存的集中位置;而系统表第一版只应该走最简单的“全列、无过滤”路径,避免把 catalog 复制复杂度带到 publication 元数据层。(Doxygen PostgreSQL)
八、补丁五:订阅端允许白名单系统表作为 apply 目标
这是最关键的另一半。PG18 的 CheckSubscriptionRelkind() 只接受普通表、分区表和 sequence;官方文档也写明 subscriber 端目标只支持 regular table。(Doxygen PostgreSQL)
8.1 修改文件一
src/backend/executor/execReplication.c
8.2 增加 include
#include "replication/logicalsysrel.h"
8.3 保守修改 CheckSubscriptionRelkind()
这个函数当前只拿到了 localrelkind / remoterelkind / 名字,没有本地 relid,因此不适合在这里直接用 OID 白名单判断。更稳的改法是:
- 尽量少改这个函数
- 把“是否允许系统表”前移到
logicalrep_rel_open()里 CheckSubscriptionRelkind()保持现有 relkind 兼容语义
也就是这一步建议 **不直接大改 CheckSubscriptionRelkind()**,只在必要时保留原表/分区表/sequence 检查。这样能减少对全局逻辑的影响。当前函数的限制条件和 sequence 精确匹配逻辑就在这里。(Doxygen PostgreSQL)
8.4 修改文件二
src/backend/replication/logical/relation.c
8.5 增加 include
#include "replication/logicalsysrel.h"
8.6 在 logicalrep_rel_open() 中前移系统表白名单检查
思路是:
远端 relation message 收到后,按本地同名 relation 打开
打开后,如果本地是 system table:
- 只有白名单表允许继续
- 非白名单仍报错
然后再执行原有的
CheckSubscriptionRelkind()
伪代码:
LogicalRepRelMapEntry *
logicalrep_rel_open(LogicalRepRelId remoteid, LOCKMODE lockmode)
{
LogicalRepRelMapEntry *entry;
Relation localrel;
Oid relid;
entry = ... existing lookup ...;
if (!OidIsValid(entry->localreloid))
return entry;
relid = entry->localreloid;
localrel = table_open(relid, lockmode);
/*
* 新增:对本地系统表做白名单控制
*/
if (IsCatalogRelation(localrel) &&
!IsLogicalRepSystemRelation(localrel))
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot use relation \"%s.%s\" as logical replication target",
entry->remoterel.nspname,
entry->remoterel.relname),
errdetail("This operation is not supported for system tables.")));
/*
* 保持原有 relkind 检查
*/
CheckSubscriptionRelkind(localrel->rd_rel->relkind,
entry->remoterel.relkind,
entry->remoterel.nspname,
entry->remoterel.relname);
...
}
8.7 为什么放在这里更合适
因为这里已经拿到了本地实际 relation,可以同时判断:
- 是否 system table
- 是否白名单
- relkind 是否兼容
而 CheckSubscriptionRelkind() 只拿到了字符型 relkind 和名字,缺少 OID / Relation 上下文,不适合做系统表白名单控制。logicalrep_rel_open() 本来就是 subscriber 端 relation mapping 的主入口。(Doxygen PostgreSQL)
九、补丁六:限制 initial copy
由于官方文档已经明确 subscriber 目标只支持 regular table,第一版不建议动 tablesync / copy path。(GitHub)
9.1 实现建议
第一版直接在创建订阅或 relation sync 阶段拒绝:
- 如果 publication 里有白名单系统表
- 且
copy_data = true - 则报错
建议在 subscription 端 relation sync 初始化附近加一层检查,伪代码:
if (is_sysrel_replication_enabled_for_subscription(subid) &&
copy_data)
ereport(ERROR,
(errmsg("initial copy is not supported for logical replication of system relations")));
9.2 原因
system table 的初始一致性问题,比增量 DML 重放更难。第一版只做 streaming apply,成本最低。
十、补丁七:限制 UPDATE/DELETE 的复制身份
这一点你在实现时一定要加。
虽然 subscriber 端 apply 框架能走普通表写入路径,但系统表如果没有合适的 replica identity / 唯一键,UPDATE/DELETE 基本无法安全定位目标行。subscriber 端现有逻辑复制本来就是围绕 table/sequence 的对象类型兼容和关系映射设计的。(Doxygen PostgreSQL)
10.1 建议做法
在 publisher 端检查白名单系统表时增加限制:
- 必须有主键,或者
- 必须
REPLICA IDENTITY FULL
建议在 publication add relation 阶段附加检查:
static void
check_logical_sysrel_replica_identity(Relation rel)
{
if (rel->rd_rel->relreplident != REPLICA_IDENTITY_FULL &&
RelationGetReplicaIndex(rel) == InvalidOid)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("cannot add system relation \"%s\" to publication",
RelationGetRelationName(rel)),
errdetail("Logical replication of system relations requires a replica identity.")));
}
然后在 check_publication_add_relation() 里对系统表调用它。
十一、建议增加一个总开关 GUC
第一版如果只写死白名单,编译期改表 OID 很麻烦。更实用的是加一个 GUC。
11.1 参数建议
logical_replication_system_relations = 'pg_catalog.my_sys_tbl,pg_catalog.my_other_tbl'
11.2 实现方式
- 定义
char *logical_replication_system_relations - postmaster/reload 级参数均可,第一版建议
PGC_SUSET - 启动时或首次使用时解析为 OID 缓存列表
11.3 优点
这样无需改代码就能切换目标系统表,适合你做实验和灰度。
十二、近似补丁汇总
下面给你一个“能指导编码”的最小补丁清单。
12.1 新增文件
src/include/replication/logicalsysrel.hsrc/backend/replication/logical/logicalsysrel.c
12.2 修改文件
src/include/utils/rel.hsrc/backend/catalog/pg_publication.csrc/backend/replication/pgoutput/pgoutput.csrc/backend/replication/logical/relation.csrc/backend/replication/logical/Makefile
12.3 关键改动点
rel.h
+#include "replication/logicalsysrel.h"
#define RelationIsLogicallyLogged(relation) \
(XLogLogicalInfoActive() && \
RelationNeedsWAL(relation) && \
(relation)->rd_rel->relkind != RELKIND_FOREIGN_TABLE && \
- !IsCatalogRelation(relation))
+ (!IsCatalogRelation(relation) || IsLogicalRepSystemRelation(relation)))
pg_publication.c
+#include "replication/logicalsysrel.h"
- if (IsCatalogRelation(targetrel))
+ if (IsCatalogRelation(targetrel) &&
+ !IsLogicalRepSystemRelation(targetrel))
ereport(ERROR, ...);
- !IsCatalogRelationOid(relid) &&
+ (!IsCatalogRelationOid(relid) || IsLogicalRepSystemRelationOid(relid)) &&
- relid >= FirstNormalObjectId;
+ (relid >= FirstNormalObjectId || IsLogicalRepSystemRelationOid(relid));
pgoutput.c
+#include "replication/logicalsysrel.h"
并在 get_rel_sync_entry() 里对系统表禁用 row filter / column list。
relation.c
+#include "replication/logicalsysrel.h"
并在 logicalrep_rel_open() 中新增:
if (IsCatalogRelation(localrel) &&
!IsLogicalRepSystemRelation(localrel))
ereport(ERROR, ...);
十三、编译后你该怎么验证
建议用一张“你自己可控的系统样式表”先做,不要一上来就碰核心 catalog。
13.1 验证顺序
先验证这几步:
- publication 能否成功加入目标系统表
- 目标系统表 INSERT 是否能被 decode 并发出
- subscriber 是否能打开本地同名表
- INSERT 是否能 apply 成功
- 再测 UPDATE / DELETE
- 最后测异常回放、重启恢复、slot 重连
13.2 最小测试 SQL
发布端:
ALTER PUBLICATION pub ADD TABLE pg_catalog.my_sys_tbl;
订阅端:
CREATE SUBSCRIPTION sub
CONNECTION '...'
PUBLICATION pub
WITH (copy_data = false);
然后在发布端对该系统表做:
INSERT INTO pg_catalog.my_sys_tbl ...;
UPDATE pg_catalog.my_sys_tbl ...;
DELETE FROM pg_catalog.my_sys_tbl ...;
十四、最重要的风险提醒
这个补丁“能跑”不代表“对所有系统表都安全”。
原因很简单:PG18 当前把 system table 排除在 publishable / subscriber target 之外,不只是保守,而是因为很多 catalog 变更并不等价于普通 heap DML。系统表内容默认不做逻辑抽取所需记录,publication 入口直接拒绝,subscriber 目标类型也被限制,官方文档也明确只支持 regular table 作为目标,这些都说明“catalog 复制”天然超出原生逻辑复制的默认语义边界。(GitHub)
所以第一版最稳的策略仍然是:
- 只放开一张或极少数白名单系统表
- 最好是你为了 DDL/元数据同步而专门设计的 catalog-like 表
- 不要直接碰
pg_class、pg_attribute、pg_depend、pg_authid这类核心 catalog
如果你愿意,我下一条可以继续直接给你一版 统一 diff 风格的 patch 草稿,按文件分段写成更像 git diff 的形式。