SQL 触发器类型及创建方法

2次阅读

触发器类型按执行时机(before/after)与事件(insert/update/delete)组合确定,共6种;选错时机将导致逻辑错误,如校验新值须用before,日志记录须用after。

SQL 触发器类型及创建方法

INSERT/UPDATE/DELETE 触发器怎么选

触发器类型不是按功能分,而是按它「在什么动作发生时执行」来定的。mysqlpostgresql 都只支持 BEFOREAFTER 两种时机,再叠加上 INSERTUPDATEDELETE 三类事件,组合出最多 6 种写法(比如 BEFORE INSERTAFTER UPDATE)。

选错时机会导致逻辑失败:比如想用新值校验但用了 BEFORE UPDATE,这时 NEW 已可用;但若用 AFTER UPDATE,就只能读不能改——因为语句已执行完。

  • BEFORE INSERT:适合做默认值填充、字段格式标准化(如转小写)、或拒绝非法数据(用 signal 抛错)
  • AFTER INSERT:适合记录日志、更新统计表、调用存储过程发通知——不能改当前行数据
  • BEFORE UPDATE:能安全修改 NEW 字段,比如自动更新 updated_at 或归一化邮箱格式
  • BEFORE DELETE:可用来备份待删行(插入到历史表),或检查外键约束逻辑

MySQL 创建触发器的硬性限制

MySQL 对触发器有几条不声张但致命的限制,踩中直接报错且提示模糊:

  • 一张表上同一种事件+时机只能有一个触发器,比如不能有两个 BEFORE INSERT;想实现多逻辑,得全写进一个触发器里
  • 不能在触发器里对触发它的那张表做 INSERT/UPDATE/DELETE(会报 Error 1442),这是 MySQL 的死锁防护机制,不是 bug
  • 触发器里不能调用含非确定性函数的存储函数,比如 NOW() 是允许的,但 UUID() 在某些版本会报错
  • 触发器体必须用 DELIMITER 改分隔符,否则遇到第一个 ; 就被截断——这是新手最常卡住的地方

示例(MySQL):

DELIMITER $$ CREATE TRIGGER user_updated_at  BEFORE UPDATE ON users FOR EACH ROW BEGIN   SET NEW.updated_at = NOW(); END$$ DELIMITER ;

PostgreSQL 的 WHEN 条件和 REFERENCING

PostgreSQL 触发器更灵活,但语法细节容易漏掉。关键点不在“能不能”,而在“怎么写才生效”:

  • WHEN 子句必须用括号包裹条件表达式,比如 WHEN (OLD.status != NEW.status),漏括号直接语法错误
  • 想在触发器函数里访问旧/新行,必须在 CREATE TRIGGER 里显式声明 REFERENCING OLD AS old NEW AS new,否则函数内 OLD/NEW 不可见
  • 触发器函数必须返回 TRIGGER 类型,且最后一行得是 RETURN NEWBEFORE)或 RETURN NULLAFTER),否则可能静默失败
  • PostgreSQL 允许同一事件多个触发器,执行顺序按名字字母序——别依赖顺序,名字别起成 a_checkz_log

触发器里的错误处理很脆弱

触发器里抛错不像应用层那么可控:一旦出错,整个原始 SQL 事务会回滚,而且错误信息往往只显示“触发器执行失败”,不带具体行号或变量值。

  • MySQL 中用 SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'xxx' 才能自定义错误;直接 selectINSERT 报错只会显示通用提示
  • PostgreSQL 中建议在触发器函数开头加 raise EXCEPTION,但注意它会让整个事务终止,连之前成功的 INSERT 都撤回
  • 别在触发器里做远程 http 调用或大表 JOIN——超时或锁等待会拖垮主 SQL,且无法重试
  • 测试时务必用真实数据量跑,小数据下没问题的触发器,百万行批量更新时可能慢 10 倍以上

真正麻烦的是:触发器逻辑藏在数据库里,应用代码看不到,上线后排查问题经常要翻三处——应用日志、binlog、还有 SHOW CREATE TRIGGER

text=ZqhQzanResources