不能靠触发器“自动更新冗余字段”来保证强一致性——它只适合低频、简单、可预判的同步场景;高频写入、批量操作、跨表聚合类需求,必须移出触发器。

MySQL 中 BEFORE UPDATE 是唯一安全写冗余字段的地方
在 AFTER UPDATE 里对原表再执行 UPDATE 会直接触发 ERROR 1442;BEFORE UPDATE 却能直接赋值给 NEW.xxx 字段,且不违反限制。
- 适用场景:用户昵称变更时,同步更新订单表里的
buyer_nickname字段(仅当该字段确实依赖主表) - 必须逐字段比对:
IF OLD.nickname != NEW.nickname THEN SET NEW.buyer_nickname = NEW.nickname;,否则无差别更新会导致冗余数据膨胀 - 注意 NULL 比较:用
IF NOT (OLD.nickname NEW.nickname)更稳妥,避免NULL != NULL返回 FALSE - 别在
BEFORE里查其他大表——子查询若没索引,高并发下会拖慢主表写入
PostgreSQL 的 AFTER 触发器能改原表,但死循环风险极高
PG 允许在 AFTER UPDATE 中执行 UPDATE target_table SET ... WHERE id = OLD.id,但必须主动防递归,否则一次更新可能触发自身 N 次。
- 必加防护:开头写
IF pg_trigger_depth() > 1 THEN RETURN; END IF; - WHERE 条件必须精确到行:
WHERE ctid = OLD.ctid或WHERE id = OLD.id AND updated_at = OLD.updated_at,避免误更新整张表 - 多语言字段同步时,别用
INSERT INTO langs ... SELECT ... FROM main——事务未提交前,SELECT可能查不到刚插入的NEW.id,改用BEFORE预生成或硬编码默认值 - 触发器函数返回
NULL会跳过后续操作,返回OLD或NEW才生效,这点极易写错
所有数据库都绕不开的批量操作盲区
LOAD DATA INFILE、INSERT INTO ... VALUES (), ()、ORM 的 bulk_create 等批量写入方式,默认不触发任何触发器——这不是 bug,是设计如此。
- MySQL 的
INSERT IGNORE和REPLACE INTO同样跳过触发器,哪怕有唯一键冲突也静默忽略 - 想覆盖这个行为,只能把批量逻辑拆成单行 + 显式事务,或改用应用层异步任务兜底
- Supabase 的
auth.users表插入不受你控制,但它的on_auth_user_created钩子可触发函数,这是比触发器更可靠的同步入口 - SQL Server 的
INSTEAD OF触发器能拦截批量插入,但需手动解析INSERTED表并重写逻辑,复杂度陡增
跨表同步别信“自动”,得靠显式事务+幂等设计
触发器无法回滚调用它的主事务。比如主表 UPDATE 成功了,触发器往日志表写失败,主操作不会回滚——数据已不一致。
- 真正需要强一致的场景(如库存扣减+订单状态更新),必须用存储过程 +
BEGIN TRANSACTION包裹全部操作 - 目标表要有联合唯一键(如
(source_id, event_time)),防止重复触发导致脏数据 - MySQL 的
INSERT ... ON DUPLICATE KEY UPDATE比先SELECT再判断更原子,但前提是多语言表用(entity_id, lang_code)当主键 - 别在触发器里调用 HTTP 请求或复杂计算——它没有超时机制,卡住就拖垮整个写链路
触发器不是同步开关,而是带锁的单行缝合针;真正要动多表、大批量、高可靠的数据流,得从应用层或物化视图重新设计,而不是在触发器里堆补丁。

















