触发器中禁止执行select查询、调用存储过程、全字段比对、访问blob/text、跨库操作及向大表插入数据;应仅保留必要字段查询、使用异步处理、批量插入并优先由应用层承担状态同步等职责。

触发器里别写 SELECT 查询
绝大多数性能崩盘都源于在 INSERT 或 UPDATE 触发器里执行了带 SELECT 的关联查询——尤其是查大表、查视图、或嵌套子查询。数据库会在每行变更时同步等结果,锁住源表+阻塞主事务。
- 只保留真正必需的字段,用
WHERE限定到单行(比如靠NEW.id查配置),避免SELECT *或无索引条件 - 如果要查外部状态,优先改用异步方式:把关键 ID 写进日志表,由后台任务处理
- MySQL 8.0+ 可考虑用
INSERT ... ON DUPLICATE KEY UPDATE替代部分“先查再更”的逻辑
避免在触发器中调用存储过程或自定义函数
看起来封装干净,实则隐藏了大量不可见开销。每次调用都会触发上下文切换、参数拷贝、权限校验,且 MySQL 对这类嵌套调用的执行计划几乎不优化。
- 把简单逻辑直接展开写进触发器体,比如时间戳生成、状态码映射、字符串拼接
- 若必须复用,确认该函数是
DETERMINISTIC且不含 SQL;否则宁可重复几行代码 - PostgreSQL 中尤其注意:含
PERFORM或SELECT的函数,在触发器里调用等于又埋了一层查询
UPDATE 触发器慎用 OLD 和 NEW 全字段比对
写 IF OLD.status != NEW.status THEN ... 没问题,但写成 IF OLD.* != NEW.* THEN(伪代码)就危险了——有些数据库会隐式序列化整行做比较,大文本字段或 JSON 列会让延迟飙升。
- 只比对业务上真正关心的字段,例如
OLD.updated_at、NEW.status - 不要依赖
OLD查原始值来“还原”数据,那说明设计有冗余;触发器应专注响应变更,而非补救模型缺陷 - MySQL 中
OLD/NEW对 BLOB/TEXT 字段访问本身就会触发临时磁盘表,能避则避
触发器里禁止写 INSERT INTO 大表或跨库操作
一个 INSERT 触发器往日志表插 10 行不打紧,但若日志表已有千万级数据且没分区,又没走索引,每次插入都会触发全表扫描式维护——这是线上最隐蔽的慢因之一。
- 审计类日志表务必按时间分区(如 MySQL 的
PARTITION BY RANGE (TO_DAYS(created_at))) - 避免在触发器中
INSERT INTO other_db.audit_log,跨库操作无法复用连接池,还可能因网络抖动卡住主事务 - 如果必须落多条记录,改用单条
INSERT ... VALUES (...), (...), (...)批量插入,别循环调用
真正难的不是写出能跑的触发器,而是判断「这件事到底该不该放触发器里做」。很多团队后期推倒重来,都是因为把本该由应用层兜底的状态同步、缓存更新、通知分发,全压给了数据库。










