聚合函数在触发器中基本不能用,因其行级同步执行与表级扫描冲突,每行变更都触发全表聚合将导致严重性能问题、锁等待、死锁及主从复制不一致,且无法实现分组聚合逻辑。

聚合函数在触发器里基本不能用,不是语法不让,而是事务模型和执行粒度根本扛不住。
触发器是行级同步执行,聚合函数却是表级扫描
每次 INSERT/UPDATE/DELETE 一行,触发器就跑一次。如果里面写 SUM()、COUNT() 或子查询套聚合,等于每行都触发一次全表扫描——比如在订单表的 AFTER INSERT 里查 SELECT SUM(amount) FROM orders WHERE user_id = NEW.user_id,1000 个订单插入就要扫 1000 次用户订单记录。
- MySQL 的 InnoDB 在这种场景下极易出现
Lock wait timeout exceeded或Deadlock found when trying to get lock - PostgreSQL 会卡在
SHARE ROW EXCLUSIVE锁上,尤其当聚合查询没走索引时 - 即使加了索引,高并发下多个触发器同时读同一张汇总表,也会争抢
consistent read版本,拖慢主事务
NEW 和 OLD 是快照,聚合结果却要实时算
触发器能直接用 NEW.amount、OLD.status,因为它们是内存里的变更前/后值,零开销。但 SELECT AVG(x) FROM t WHERE y = NEW.y 是实打实去磁盘捞数据,还可能被其他事务正在改的行阻塞。
-
BEFORE INSERT中没有OLD,AFTER DELETE中没有NEW,强行引用报Unknown column 'OLD.xxx' in 'field list' - 想“实时更新用户总消费”,别查表,直接用
NEW.user_total := OLD.user_total + NEW.amount(仅限BEFORE UPDATE) - 真要依赖外部状态,查询必须命中主键或唯一索引,且加
LIMIT 1;否则宁可抛异常,也别让触发器变慢查询入口
事务上下文共享导致复制和一致性风险
触发器代码运行在主 SQL 的同一事务里,所有修改原子提交或回滚。但聚合函数常依赖非确定性逻辑(如 NOW()、RAND()),在 MySQL 主从复制中若用语句级(SBR)模式,从库重放时结果不一致,轻则数据偏差,重则复制中断。
- 自定义函数必须显式声明
DETERMINISTIC或READS SQL DATA,否则CREATE FUNCTION都会被拒 - 用
binlog_format = ROW可绕过部分问题,但 binlog 体积暴涨,网络同步压力翻倍 - PostgreSQL 的触发器函数若调
pg_notify()是安全的,但一旦嵌套PERFORM SELECT ... INTO,就可能触发40P01 deadlock_detected
最常被忽略的一点:触发器里根本没法做“分组聚合”。你不能在 BEFORE INSERT 里写 SELECT dept_id, AVG(salary) FROM emp GROUP BY dept_id 然后拿结果去比对——它要么报错“subquery must return only one row”,要么锁住整个 emp 表。需要聚合的地方,提前算好存字段,或者扔给应用层异步补全。











