必须用old/new差值做增量修正,否则在after update触发器中执行select sum()会因mysql引擎级自引用限制直接报error 1442;该限制禁止对正被dml操作的表进行任何select(含子查询间接引用),非配置或版本问题,而是innodb/myisam强制拦截。

不能靠触发器重算全量SUM,必须用OLD/NEW差值做原子修正——否则必报ERROR 1442、锁表、binlog膨胀、高并发丢数。
为什么AFTER UPDATE里SELECT SUM()会直接报错
MySQL硬性禁止在触发器中对当前正被DML操作的表执行任何SELECT(哪怕只是子查询里间接引用)。比如在orders表的AFTER UPDATE触发器里写SELECT SUM(amount) FROM orders WHERE customer_id = NEW.customer_id,引擎会立刻抛出ERROR 1442。这不是配置问题,是存储引擎层的自引用限制。
常见错误现象包括:
- 主SQL成功返回,但触发器静默失败,汇总数据持续不一致
- 多个并发UPDATE同一客户订单时,各触发器读到不同事务快照,导致
total_spent反复覆盖、最终值丢失 - 子查询没走索引,拖慢主事务,延长
orders表行锁持有时间
INSERT/UPDATE/DELETE三类触发器必须配齐且逻辑对称
只写INSERT触发器而漏掉DELETE,汇总值会越积越多;只处理UPDATE却忽略customer_id变更,就会漏减旧客户、漏加新客户。
正确做法是按变更类型分别处理:
-
INSERT:用NEW.amount累加到目标汇总行,推荐用INSERT ... ON DUPLICATE KEY UPDATE,避免因主键不存在而静默失败 -
DELETE:用OLD.amount从目标汇总行减去,注意OLD字段在INSERT触发器中不可用 -
UPDATE:先判断OLD.amount != NEW.amount,再计算差值;若OLD.customer_id != NEW.customer_id,需双路更新——减旧客户、加新客户
汇总表结构和触发器写法必须规避“多行匹配”风险
如果汇总表没有主键或唯一索引,UPDATE summary SET total = total + ... WHERE category_id = NEW.category_id可能匹配多行,导致总和错乱。
必须确保:
- 汇总表用
InnoDB引擎,且分组维度字段(如customer_id、status)组合成PRIMARY KEY或UNIQUE约束 - 所有更新值只来自
NEW或OLD,不查原表、不调函数、不跨库 - 显式处理NULL:
IFNULL(NEW.amount, 0),否则NULL + 100结果仍是NULL
触发器上线后几乎无法热更新,逻辑必须极度克制
字段名变更、条件调整、新增关联维度,都得停写、改触发器、重新验证——线上环境基本不可行。真正可维护的做法是把核心修正逻辑抽到存储过程中,触发器只做简单调用,例如:CALL update_customer_total(OLD.customer_id, OLD.amount, NEW.customer_id, NEW.amount)。
最容易被忽略的一点:触发器一旦写进生产库,就等于把业务规则硬编码进了数据库schema。它不支持版本管理、难以测试、调试日志有限,且MySQL 8.0+对非确定性操作限制更严。如果业务允许分钟级延迟,优先用EVENT定时刷新,或把聚合逻辑移至应用层。











