'最稳定就是FULL'模式,虽然会有多一点的WAL,但是胜在稳定,踩坑多次后的强烈推荐
------ publication、replica identity 与 DELETE 失败问题详解
一、背景
在生产环境中为 PostgreSQL(分区表)引入 Debezium CDC,将数据同步到 Kafka。目标表为:
-
public.table_mailbox_message
-
分区表(按 date_created range 分区)
-
存在 UPDATE / DELETE 场景
使用:
-
PostgreSQL 14
-
Debezium PostgreSQL Connector
-
插件:pgoutput
-
CDC 模式:逻辑复制(Logical Replication)
二、问题现象
开启 CDC 后,业务侧开始出现 DELETE 失败,错误信息如下:
ERROR: cannot delete from table "xxx"
because it does not have a replica identity and publishes deletes
HINT: To enable deleting from the table, set REPLICA IDENTITY using ALTER TABLE.
表面看像是:
-
表没有主键
-
或 replica identity 未设置
但实际上,被影响的表并不是本次 CDC 的目标表。
三、根因分析(核心机制)
1️⃣ Debezium CDC 本质依赖 PostgreSQL Publication
Debezium 并不是直接"监听表",而是通过 Postgres 的 publication + replication slot 读取 WAL。
Publication 决定了:
-
哪些表参与 CDC
-
是否发布 insert / update / delete
一旦某个表被纳入 publication,且 publication 发布 delete:
-
PostgreSQL 就要求该表必须具备 replica identity
-
否则直接禁止 DELETE / UPDATE(数据库级保护)
2️⃣ 没有显式控制 publication,Debezium 可能扩大发布范围
如果:
-
publication 未手动创建
-
或 connector 使用了错误/历史的 publication
-
或启用了 publication 自动创建
Debezium 可能会:
-
创建包含多表甚至全库表的 publication
-
或复用已有 publication(已包含其他业务表)
结果是:
并非目标表的业务表被纳入 CDC,进而触发 Postgres 的 DELETE 限制
3️⃣ PostgreSQL 的保护行为(不是 Bug)
Postgres 在逻辑复制场景下的原则是:
如果 DELETE / UPDATE 会被发布,但数据库无法唯一定位被修改的行,就直接拒绝该操作。
因此:
-
没有主键 / 唯一索引
-
或 replica identity = DEFAULT 且无 PK
→ DELETE / UPDATE 会被禁止
这是 设计层面的强一致性保护,不是 Debezium 或业务代码问题。
四、分区表 + publication 的一个关键细节
对分区表:
CREATE PUBLICATION pub_name
FOR TABLE public.table_mailbox_message;
在 pg_publication_tables 中看到的,实际上是 所有分区子表:
table_mailbox_message_2023
table_mailbox_message_2024
...
table_mailbox_message_default
这在 Postgres 中是正常行为,但会带来两个问题:
-
下游 CDC 看到的是分区表名
-
发布范围容易混乱、难以人工检查
正确做法:
ALTER PUBLICATION pub_name
SET (publish_via_partition_root = true);
这样 CDC 事件会稳定归属父表。
五、错误示范(踩坑点总结)
❌ 依赖 Debezium 自动创建 publication
publication.autocreate.mode = all_tables / filtered
风险:
-
发布范围不可控
-
可能包含历史表 / 无主键表
-
DELETE 直接被数据库拦截
❌ 使用历史/混用的 publication
-
publication 名字存在历史包袱
-
曾经被用于其他 CDC / 测试
-
已包含多个业务表
❌ 滥用
REPLICA IDENTITY FULL
虽然 FULL 可以拿到 UPDATE 的 before 全行数据,但代价极高:
-
UPDATE / DELETE 会把整行旧值写入 WAL
-
大字段(bytea / text / json)极易造成 WAL 暴涨
-
replication slot 积压 → 磁盘爆满风险
FULL 不是常规方案,只是兜底。
六、正确、可控的 CDC 方案(推荐)
1️⃣ 手动创建 publication(最重要)
CREATE PUBLICATION dbz_pub_mailbox_message
FOR TABLE public.table_mailbox_message;
ALTER PUBLICATION dbz_pub_mailbox_message
SET (publish_via_partition_root = true);
2️⃣ Debezium 明确指定 publication
"publication.name": "dbz_pub_mailbox_message",
"publication.autocreate.mode": "disabled"
禁止 Debezium 自动创建或扩展 publication。
3️⃣ 使用主键作为 replica identity
-
有主键 → DEFAULT 即可
-
无需 REPLICA IDENTITY FULL
-- 查看当前 replica identity
SELECT relreplident
FROM pg_class
WHERE oid = 'public.table_mailbox_message'::regclass;
4️⃣ CDC 回退方案(生产必备)
一旦出现问题:
# 删除 connector
curl -X DELETE http://localhost:8083/connectors/<name>
# 删除 replication slot
SELECT pg_drop_replication_slot('<slot_name>');
即可:
-
解除 WAL 积压
-
恢复数据库正常写入
七、关键经验总结(结论)
✅ Publication 必须手动创建,且只包含目标表
✅ 禁止 Debezium 自动创建 / 修改 publication
✅ CDC 是数据库级行为,会影响 DML 语义
✅ DELETE 失败是 Postgres 的保护机制,不是配置错误
✅
REPLICA IDENTITY FULL
只适用于无主键且必须拿 before 全行的场景
✅ 分区表务必使用
publish_via_partition_root
八、一句话总结
CDC 不是"只读旁路系统",而是会改变数据库对 UPDATE/DELETE 的约束规则。
在 PostgreSQL 中,publication 的控制权必须掌握在 DBA/工程师手中,否则影响的是整个业务写入链路。