触发器无法作为可靠cdc方案的核心限制在于:它不感知事务边界、提交时间、binlog位点或wal lsn,无法保证全局顺序一致性和断点续传能力。

直接上结论:触发器能捕获行级变更,但不等于开箱即用的CDC方案;它适合中小规模、低频写入、强一致性要求的场景,不适合高吞吐OLTP表或需要精确事务顺序的ETL链路。
触发器捕获变更的核心限制在哪
触发器本质是语句执行时的同步钩子,不是异步日志解析器。它能看到NEW和OLD,但看不到事务边界、提交时间、binlog位点或WAL LSN。
- MySQL触发器无法读取
binlog,也不能访问GTID,所以不能保证与下游消费端的全局顺序一致 - PostgreSQL触发器在
AFTER时机可读txid_current(),但无法绑定到具体WAL位置,下游无法做断点续传 - SQL Server触发器依赖
inserted/deleted临时表,但这些表只在触发器生命周期内有效,不能跨批次持久化 - 所有数据库的触发器都**不记录操作类型字段(INSERT/UPDATE/DELETE)的原始语义**——比如UPDATE可能由应用层拼成INSERT+DELETE模拟,触发器只会看到两行
NEW,无法还原动作意图
真正可用的增量标记字段怎么加
别依赖触发器“猜”变更,而是让每条记录自带可排序、可过滤的时间戳或版本号。最稳的方式是组合使用:
-
lastupdatetimestamp必须是BEFORE INSERT OR UPDATE触发器维护,且函数里用clock_timestamp()(非now()),避免同一事务内多行时间相同 - 对MySQL,优先用
ON UPDATE CURRENT_TIMESTAMP列,比触发器轻量、无递归风险;但注意它不覆盖INSERT时的值,需配合DEFAULT CURRENT_TIMESTAMP - PostgreSQL中若需区分INSERT/UPDATE,可在触发器函数里加判断:
IF TG_OP = 'INSERT' THEN NEW.op_type := 'I'; ELSE NEW.op_type := 'U'; END IF;,并新增op_type列 - 绝对不要用
serial或bigserial当变更序号——它只反映插入顺序,UPDATE不会更新,且并发下不保序
触发器写进变更日志表的实操要点
把变更写入一张cdc_log表是最常见做法,但容易踩几个硬坑:
- 日志表必须和业务表在**同一schema、同一事务内**写入,否则存在丢失风险;PostgreSQL可直接
INSERT INTO cdc_log ...,MySQL需确保引擎支持XA(如InnoDB),且不能用MyISAM - 日志字段建议固定为:
table_name、pk_value(JSON或拼接字符串)、op_type('I'/'U'/'D')、ts(clock_timestamp())、txid(PG用txid_current(),MySQL暂无等效) - 避免在触发器里做复杂JOIN或子查询——会拖慢主表DML;如需关联维度表,改用异步任务定期补全
cdc_log中的描述字段 - MySQL触发器里禁止对原表再做
UPDATE或INSERT(报错Can't update table in stored function/trigger),所以cdc_log必须是另一张独立表
为什么不能直接拿触发器当CDC服务用
因为CDC不是“有没有变更”,而是“能不能可靠重放”。触发器缺失三个关键能力:
- 没有幂等性保障:同一条变更被重复消费时,
cdc_log会再写一遍,下游需自行去重(靠(table_name, pk_value, txid)联合唯一索引可缓解,但不治本) - 没有失败隔离:触发器报错会导致主DML回滚,业务不可用;而专业CDC工具(如Debezium、pgoutput)挂了只影响下游,不影响源库
- 没有批量压缩:每行变更都写日志,10万行UPDATE产生10万条日志;而WAL解析可合并为单条逻辑变更(如“更新用户表中5000个用户的status字段”)
真正上线前,务必压测触发器在峰值写入下的延迟增幅——它会真实拖慢你的OLTP性能,这点常被忽略。










