触发器修改其他表必须显式加事务控制,否则主事务不回滚导致数据状态分裂;应避免与应用逻辑重复修改字段、禁用“兜底”思维、禁止调用含IO的函数,并始终面向集合编程。

触发器修改其他表时必须显式加事务控制
触发器里写 UPDATE stats SET last_login = GETDATE() WHERE user_id = NEW.id 看似简单,但若该语句失败,主事务不会自动回滚——SQL Server 和 PostgreSQL 默认不将触发器错误传播到外层事务,MySQL 5.7+ 也只在严格模式下才中断。结果就是:用户记录插入成功,统计表更新静默失败,数据状态分裂。
实操建议:
- SQL Server 中用
THROW或RAISERROR抛异常,并确认数据库XACT_ABORT设为 ON - PostgreSQL 触发器函数末尾加
RETURN NEW或RETURN NULL,避免隐式提交;若需跨表更新,改用PERFORM+EXCEPTION块捕获 - MySQL 中禁止在触发器里用
INSERT INTO ... SELECT直接操作大表,否则锁等待会拖垮主事务,且错误日志常被吞掉
避免触发器与应用逻辑重复修改同一字段
常见错误是:应用代码更新 users.status 后,触发器又去重置 updated_at、同步 audit_log、甚至调用存储过程刷新缓存。一旦某环节漏掉幂等设计,updated_at 可能被反复覆盖,或缓存被刷错版本。
实操建议:
- 所有字段级变更必须明确归属:由应用负责业务态(如 status、balance),触发器只管衍生态(如 created_by、is_archived)
- 禁用“触发器兜底”思维——比如应用没传
created_by,就靠触发器填CURRENT_USER。这会让字段来源不可追溯,ORM 更新时还可能因乐观锁冲突报错 - 上线前用
SELECT * FROM sys.triggers(SQL Server)或\dtr(PostgreSQL)逐个核对触发器动作,对照应用代码确认无重叠写入点
禁止在触发器中调用含副作用的函数或外部服务
send_notification() 这类函数看似封装干净,实则把邮件发送、HTTP 请求、Kafka 写入全塞进事务里。一旦网络抖动或下游超时,主 SQL 就卡住,锁持有时间飙升,甚至触发 lock wait timeout。
实操建议:
- 触发器内只允许纯计算:字符串拼接、时间戳生成、枚举映射。任何涉及 IO 的操作(包括查远程链接服务器、调
xp_cmdshell)一律移出 - MySQL 中若函数用了
NOW()或未声明DETERMINISTIC,主从复制可能不一致;PostgreSQL 中含PERFORM dblink_connect()的触发器,会导致 WAL 日志膨胀 - 真正要发通知?触发器只写一行到轻量队列表:
INSERT INTO notify_queue (event_type, target_id) VALUES ('user_updated', NEW.id),再由独立作业消费
多行操作时不能依赖单行假设
用 SELECT @id = id FROM inserted 获取 ID,再 UPDATE 关联表——这种写法在线上批量导入时必然失效。inserted 表可能含上千行,@id 只存最后一行值,其余数据绕过校验或写入错误关联记录。
实操建议:
- 始终面向集合编程:SQL Server 用
UPDATE t SET t.updated_at = GETDATE() FROM targets t INNER JOIN inserted i ON t.id = i.id - MySQL/PostgreSQL 用
NEW和OLD配合IN子查询时,必须加EXISTS或JOIN,避免WHERE id IN (SELECT id FROM inserted)引发二次扫描 - 测试必须覆盖多行场景:至少用
INSERT INTO users (...) VALUES (...), (...), (...)验证,不能只测单条INSERT
真正难防的不是明显错误,而是那些“每次只错一行”的隐性副作用——比如触发器在凌晨批量同步时悄悄跳过部分记录,日志里却只显示“执行成功”。这类问题往往要等报表对不上、审计差一笔,才被发现。

















