触发器中禁止聚合查询和复杂逻辑,应改用冗余字段、覆盖索引或应用层最终一致性;避免函数调用、非索引条件及跨表强一致校验,控制嵌套深度并确保条件走索引。

触发器里别写聚合查询
直接在 INSERT 或 UPDATE 触发器里跑 SUM()、COUNT() 这类全表扫描操作,等于每次改一行就扫一遍关联表。尤其当被聚合的表有几十万行,触发器延迟会从毫秒级跳到秒级。
- 用物化视图或冗余字段替代实时聚合:比如订单表更新时,只更新用户表里的
total_spent字段,而不是每次去算SUM(order_amount) - 如果必须查聚合结果,先加覆盖索引——确保
WHERE条件字段 + 聚合字段都在同一个索引里,避免回表 - MySQL 8.0+ 可考虑用
CTE配合MATERIALIZED提前固化中间结果,但注意它不自动刷新
慎用 AFTER 触发器做跨表强一致性校验
AFTER INSERT 里调用另一个事务去更新下游表,容易卡死或死锁。常见错误是:在订单插入后立刻去扣库存,而库存表又被其他服务高频更新。
- 把强一致性逻辑下沉到应用层,用最终一致性+补偿任务代替:比如发 MQ 消息异步扣减,失败重试
- 真要用触发器同步,改用
BEFORE触发器,在当前事务内完成校验和预更新,避免额外事务开销 - PostgreSQL 中可利用
pg_advisory_lock()对业务键加轻量锁,防止并发修改同一聚合状态
避免在触发器中调用存储过程或外部函数
像 CALL update_user_stats() 或自定义函数 get_discount_rate(),一旦内部含循环、游标或多次查询,性能会指数恶化。MySQL 的触发器甚至不支持显式事务控制,出错难回滚。
- 把复杂逻辑拆成纯 SQL 表达式:用
CASE WHEN替代简单分支,用窗口函数替代子查询嵌套 - 确认数据库版本对函数内联的支持——MySQL 8.0.22+ 支持部分标量函数自动内联,但
DETERMINISTIC必须显式声明 - 若函数依赖外部系统(如调用 HTTP),绝对禁止放入触发器:超时或网络抖动会导致整个 DML 卡住
触发器条件判断要走索引,不能靠 WHERE 后过滤
写成 IF NEW.status = 'paid' THEN ... END IF; 看似没问题,但如果这个判断本该提前排除 95% 的记录,却放在触发器体里执行,意味着每条 INSERT 都得进触发器再判断。
- 用
WHEN子句(PostgreSQL)或在 MySQL 中用IF包裹整段逻辑,但前提是判断字段已建索引且区分度高 - 避免在条件中用函数:比如
IF YEAR(NEW.created_at) = 2024会让索引失效,改用范围比较NEW.created_at >= '2024-01-01' - SQL Server 的
INSTEAD OF触发器能拦截原始语句,更适合做前置过滤,但要注意它不继承原语句的权限上下文
最常被忽略的是触发器的执行顺序和嵌套深度——比如 A 表触发器改 B 表,B 表又有触发器改 C 表,三层嵌套下错误堆栈极难定位,建议用 SHOW TRIGGERS 或 pg_trigger 定期审计链路长度。


















