一、问题背景
在 PostgreSQL 中使用 Debezium CDC(Logical Replication) 时,为了获取 UPDATE / DELETE 的 before 值,通常会设置:
ALTER TABLE xxx REPLICA IDENTITY FULL;
在普通表上,这样做往往立刻生效。但当目标表是 分区表(Partitioned Table) 时,很多人会遇到一个困惑:
父表已经设置了 REPLICA IDENTITY FULL,
Debezium 仍然拿不到 before,UPDATE 事件中 before = null。
本文就是对这个问题的完整复盘。
二、现象描述
-
父表 public.table_mailbox_message:
ALTER TABLE public.table_message REPLICA IDENTITY FULL;
-
Debezium 正常消费到 UPDATE:
{ "op": "u", "before": null, "after": {...} }
-
Kafka topic、publication、slot 均确认无误
-
Debezium 配置中也已开启:
"include.unchanged.toast": "true"
但 before 始终是 null。
三、根因分析:分区表的"真实写入表"是谁?
1️⃣ PostgreSQL 分区表的核心事实
在 PostgreSQL 中:
-
父表只是逻辑入口
-
真正存数据、执行 UPDATE/DELETE 的是子分区表
例如:
table_mailbox_message ← 父表
├── table_message_2023
├── table_message_2024
├── ...
└── table_message_default
当你执行:
UPDATE public.table_message ...
实际被修改的是某一个子分区表,而不是父表。
2️⃣ REPLICA IDENTITY 的判断发生在"被修改的表"上
PostgreSQL 在逻辑复制中是否输出 old row(before),取决于:
实际被 UPDATE / DELETE 的那张表的 relreplident 设置
而不是父表。
四、关键验证(一步就能看出来)
SELECT
c.relname AS partition,
c.relreplident
FROM pg_inherits i
JOIN pg_class c ON c.oid = i.inhrelid
WHERE i.inhparent = 'public.table_message'::regclass
ORDER BY c.relname;
典型输出:
table_message_2026 | d
table_message_2027 | d
table_message_2028 | d
table_message_default | d
说明:
-
所有分区仍是 DEFAULT
-
逻辑解码 不会输出 before
五、结论:父表 FULL ≠ 分区 FULL
❌ 错误理解
设置父表 REPLICA IDENTITY FULL,就能拿到 before
✅ 正确理解
必须在"所有可能被写入的分区表"上设置 REPLICA IDENTITY FULL
六、解决方案
方案一:批量设置所有分区为 FULL
ALTER TABLE public.table_message_2023 REPLICA IDENTITY FULL;
ALTER TABLE public.table_message_2024 REPLICA IDENTITY FULL;
...
ALTER TABLE public.table_message_default REPLICA IDENTITY FULL;
(可按年份逐条执行,生产更安全)
方案二:只对"当前活跃分区"设置 FULL(推荐渐进)
例如当前写入集中在:
-
table_mailbox_message_2028
-
table_mailbox_message_default
那只对这两个分区设置 FULL,先验证 before 是否正常、WAL 是否可控,再决定是否扩展到历史分区。
七、Debezium 侧的必要配置
对于包含 bytea / text / json 等 TOAST 字段的表,建议开启:
"include.unchanged.toast": "true"
否则即便 FULL 生效,Debezium 也可能拿不到完整 old row。
八、重要风险提示(生产必看)
REPLICA IDENTITY FULL 的代价非常高:
-
UPDATE / DELETE 会把 整行旧值写入 WAL
-
大字段会显著放大 WAL
-
replication slot 可能积压
-
极端情况下导致磁盘占满
因此:
-
FULL 是兜底方案,不是常规方案
-
有主键时,优先用主键
-
若只需要少量字段的 before,优先考虑 trigger + audit 表
九、关于新分区的额外注意事项
PostgreSQL 不会自动让新建分区继承父表的 REPLICA IDENTITY 设置。
这意味着:
-
新分区建出来后,默认还是 DEFAULT
-
若忘记设置 FULL,新分区的 UPDATE again 没 before
解决方式:
-
在分区创建脚本中显式加:
ALTER TABLE 新分区 REPLICA IDENTITY FULL;
-
或定期巡检分区的 relreplident
十、总结
-
CDC 中 before 是否存在,由实际被修改的表决定
-
对分区表,父表设置 REPLICA IDENTITY FULL 是不够的
-
必须对子分区逐一设置
-
FULL 有明显的 WAL 与磁盘风险,务必评估
在 PostgreSQL + CDC 场景下,分区表的"元数据继承"远没有看起来那么自动。