不能直接用NEW.total_amount = SUM(...),因NEW是单行上下文而SUM为聚合函数,须通过SELECT...INTO配合COALESCE赋值;UPDATE触发器必须用BEFORE;并发下需SELECT...FOR UPDATE加锁或改用视图/异步方案。
不能。MySQL 触发器中 NEW 是单行上下文,SUM() 是聚合函数,必须配合 SELECT ... INTO 才能赋值。常见错误是写成 SET NEW.total_amount = SUM(detail.price * detail.qty);,直接报错 “Invalid use of group function”。
正确做法是先查出明细行的合计值,再赋给 NEW.total_amount:
SELECT COALESCE(SUM(price * qty), 0) INTO @total FROM order_detail WHERE order_id = NEW.id;
COALESCE 防止 NULL 导致整字段变 NULL;@total 是用户变量,仅用于临时中转(不能直接 SET NEW.total_amount = @total,需显式赋值)。
必须用 BEFORE UPDATE。因为你要修改即将写入的 NEW.total_amount 值,而 AFTER 触发时数据已落库,NEW 不可写,且再改也没意义。
注意:如果业务逻辑要求“总金额只读、禁止手动改”,就在触发器里加校验:
OLD.total_amount != NEW.total_amount,然后 SET NEW.total_amount = OLD.total_amount 强制回滚修改SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'total_amount is auto-calculated';
触发器本身不加锁,但计算依赖 order_detail 表。如果在触发器执行期间,另一事务删了某条明细,会导致 total_amount 计算偏小 —— 这是典型幻读风险。
解决办法只有两个:
SELECT ... FOR UPDATE 锁住相关明细行(例如:SELECT 1 FROM order_detail WHERE order_id = NEW.id FOR UPDATE;)别指望靠 READ COMMITTED 隔离级别躲开这个问题 —— 它对触发器内查询无效。
有。触发器在高并发更新场景下容易成为瓶颈,尤其当 order_detail 行数多、索引缺失时,每次更新都要全表扫描明细。
推荐组合方案:
total_amount 字段,改用视图实时计算:CREATE VIEW order_with_total AS SELECT o.*, COALESCE(d.total, 0) AS total_amount FROM orders o LEFT JOIN (SELECT order_id, SUM(price * qty) AS total FROM order_detail GROUP BY order_id) d ON o.id = d.order_id;
真正难的不是怎么写触发器,而是想清楚:这个“自动计算”到底要多准、多快、多稳 —— 三者不可兼得,得选一个妥协点。