SQL 触发器在业务逻辑封装应用

1次阅读

绝大多数业务逻辑不该放触发器里,它只适合日志记录、状态快照等旁路保障,不适合主流程决策;滥用会导致调试难、事务耦合紧、执行不可见等问题。

SQL 触发器在业务逻辑封装应用

触发器该不该用来做业务逻辑?

不能一概而论,但绝大多数业务逻辑不该放触发器里。它适合做「旁路保障」——比如日志记录、状态快照、跨表一致性校验;不适合做「主流程决策」,比如判断用户是否可下单、调用外部服务、执行复杂分支逻辑。一旦把核心业务规则塞进 INSERTUPDATE 触发器,调试难、测试难、回滚难,连 EXPLAIN 都看不出它在干啥。

  • 触发器执行不可见:应用层看不到它偷偷改了什么,select 结果可能和你刚 INSERT 的数据对不上
  • 错误不友好:报错时只显示 Error: trigger procedure ... failed,没行号、没上下文
  • 事务耦合紧:触发器和原 sql 共享同一事务,它出错整个语句回滚,但你未必想让主操作也失败

mysql vs postgresql 触发器写法差异在哪?

关键不在语法糖,而在执行时机控制粒度和返回值约定。

  • MySQL 的 BEforE 触发器能修改 NEW 行数据(比如自动填充 created_at),AFTER 则不能;PostgreSQL 的 BEFORE 同样可改 NEW,但必须显式 RETURN NEW,否则插入失败
  • PostgreSQL 支持 FOR EACH STATEMENTFOR EACH ROW,MySQL 只支持行级;批量更新时,语句级触发器能避免 N 次重复开销
  • MySQL 不允许在触发器里调用存储函数以外的外部服务(比如 http 请求),PostgreSQL 虽可通过 curl 扩展实现,但会严重拖慢事务,实际没人这么干

示例(PostgreSQL 自动更新修改时间):

CREATE OR REPLACE FUNCTION update_modified_column() RETURNS TRIGGER AS $$ BEGIN     NEW.updated_at = NOW();     RETURN NEW; END; $$ language 'plpgsql'; <p>CREATE TRIGGER update_users_modtime BEFORE UPDATE ON users FOR EACH ROW EXECUTE FUNCTION update_modified_column();

哪些错误现象说明你滥用触发器了?

不是语法报错,而是线上行为开始“不对劲”。

  • 应用层执行一条 UPDATE数据库 CPU 突然飙升 5 秒,查 pg_stat_activity 发现一堆 idle in transaction —— 很可能是触发器里写了低效循环或未索引的子查询
  • 同一个 user_id 在日志表里出现两条时间戳完全一致的记录,但业务上不可能并发改同一个人 —— 触发器没加 IF TG_OP = 'UPDATE' 判断,INSERT 也误触发了
  • 导出数据后重新导入,触发器反复执行导致计数字段翻倍(比如 total_orders 加了两次)—— 忘了关掉 LOAD DATAcopy 期间的触发器,也没用 DISABLE TRIGGER

替代触发器的更可控方案有哪些?

把逻辑提到应用层或数据库外,不是为了“去掉触发器”,而是把控制权拿回来。

  • 用 ORM 的钩子(如 djangosave()laravelbooted)替代简单字段填充,便于单元测试和打点监控
  • 对强一致性要求高的场景(如库存扣减),用 SELECT ... FOR UPDATE + 显式事务,比靠触发器“事后补救”更可靠
  • 审计类需求(谁什么时候改了什么),优先考虑数据库自带的 pg_audit(PostgreSQL)或 general_log(MySQL),而不是自己写触发器记日志——前者不参与事务,不影响性能,且格式统一

触发器真正的价值,是兜底。它不该是你第一个想到的方案,而应是你试过所有更透明、更易测、更易维护的方式之后,还不得不留的一道窄门。门后那点逻辑,越薄越好。

text=ZqhQzanResources