PostgreSQL 分区表 + Debezium CDC:为什么 REPLICA IDENTITY FULL 不生效?

一、问题背景

在 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 场景下,分区表的"元数据继承"远没有看起来那么自动。

相关推荐
heartbeat..4 小时前
Spring AOP 全面详解(通俗易懂 + 核心知识点 + 完整案例)
java·数据库·spring·aop
麦聪聊数据6 小时前
MySQL并发与锁:从“防止超卖”到排查“死锁”
数据库·sql·mysql
AC赳赳老秦7 小时前
DeepSeek 私有化部署避坑指南:敏感数据本地化处理与合规性检测详解
大数据·开发语言·数据库·人工智能·自动化·php·deepseek
YMatrix 官方技术社区8 小时前
YMatrix 存储引擎解密:MARS3 存储引擎如何超越传统行存、列存实现“时序+分析“场景性能大幅提升?
开发语言·数据库·时序数据库·数据库架构·智慧工厂·存储引擎·ymatrix
辞砚技术录9 小时前
MySQL面试题——索引2nd
数据库·mysql·面试
linweidong9 小时前
C++thread pool(线程池)设计应关注哪些扩展性问题?
java·数据库·c++
欧亚学术10 小时前
突发!刚刚新增17本期刊被剔除!
数据库·论文·sci·期刊·博士·scopus·发表
黑白极客10 小时前
怎么给字符串字段加索引?日志系统 一条更新语句是怎么执行的
java·数据库·sql·mysql·引擎
大厂技术总监下海11 小时前
数据湖加速、实时数仓、统一查询层:Apache Doris 如何成为现代数据架构的“高性能中枢”?
大数据·数据库·算法·apache
LeenixP11 小时前
RK3576-Debian12删除userdata分区
linux·运维·服务器·数据库·debian·开发板