不能用触发器直接做跨表聚合统计,只能基于单行变更做目标汇总表的原子更新;因after insert触发器运行在单行上下文,子查询会引发锁表、脏读、丢失更新等问题,正确做法是用insert on duplicate key update配合new字段单点累加。

不能用触发器直接做跨表聚合统计,只能基于单行变更做目标汇总表的原子更新——否则必然漏数、写错或锁死写入链路。
为什么AFTER INSERT触发器里不能写SELECT COUNT(*) FROM orders JOIN users
触发器运行在单行上下文,NEW只包含刚插入的那一行数据。你试图在orders表的AFTER INSERT触发器里执行SELECT COUNT(*) FROM orders JOIN users,MySQL 会直接报语法错误,或读到未提交的脏数据。更严重的是:这类子查询会持有表级元数据锁(Waiting for table metadata lock),高并发插入时整个orders表写入会被卡住。
常见错误现象包括:
- 汇总值比实际少(子查询被事务隔离级别挡住了部分行)
-
SHOW PROCESSLIST看到大量线程卡在Waiting for table metadata lock - 多个触发器同时更新同一汇总行,出现“丢失更新”(比如两个
INSERT几乎同时发生,都读到旧值+1,最终只+1而非+2)
正确做法:用INSERT ON DUPLICATE KEY UPDATE做单点累加
汇总逻辑必须拆解成“仅依赖NEW字段 + 单条目标记录更新”,且汇总表主键必须覆盖全部统计维度。例如按customer_id和status汇总订单数与金额,汇总表summary_by_customer_status必须定义:PRIMARY KEY (customer_id, status)。
实操要点:
- 用
INSERT INTO ... ON DUPLICATE KEY UPDATE替代UPDATE,避免因主键不存在而静默失败 - 所有更新值都来自
NEW,不查原表、不调子查询(如order_count = order_count + 1,不是order_count = (SELECT COUNT(...) FROM ...)) - 显式处理空值:
IFNULL(NEW.amount, 0),否则NULL + 100结果是NULL - 禁止跨库写入(如
INSERT INTO other_db.summary),RDS等托管服务通常禁用
示例语句:
CREATE TRIGGER trg_orders_after_insert_summary
AFTER INSERT ON orders
FOR EACH ROW
BEGIN
INSERT INTO summary_by_customer_status (customer_id, status, order_count, total_amount)
VALUES (NEW.customer_id, NEW.status, 1, IFNULL(NEW.amount, 0))
ON DUPLICATE KEY UPDATE
order_count = order_count + 1,
total_amount = total_amount + IFNULL(NEW.amount, 0);
END;
INSERT IGNORE / REPLACE INTO会跳过触发器,这点极易忽略
INSERT IGNORE和REPLACE INTO对因唯一键冲突而跳过的行,**完全不触发任何触发器**。这意味着:你靠触发器维护的汇总值,在这些语句下会彻底失准。
比如用户用REPLACE INTO orders更新订单,本意是“覆盖旧记录”,但触发器不会执行,汇总表里的order_count不会-1再+1,而是直接少算一次。
务必确认业务代码中所有写入路径都走标准INSERT/UPDATE/DELETE,并为三类操作分别建触发器:
-
AFTER INSERT:加总数(total += 1) -
AFTER UPDATE:只在关键字段变化时更新(如IF OLD.status != NEW.status AND NEW.status = 'paid'才加成交额) -
AFTER DELETE:回退原值(注意IF OLD.amount IS NOT NULL判空)
多表来源的汇总必须每个表单独建触发器,且共用同一张汇总表结构
比如orders和returns两张表都要影响summary_by_customer,就得分别建两个触发器:trg_orders_after_insert_summary和trg_returns_after_insert_summary,都往同一张summary_by_customer写,但各自只处理自己表的NEW字段。
关键约束:
- 不能在一个触发器里同时
INSERT又UPDATE又DELETE汇总表——MySQL禁止在触发器内混合DML操作目标表 - 不能让
orders触发器去UPDATE returns_summary——跨表更新可能触发递归或外键约束,直接报Cannot update table in stored function/trigger - 汇总表主键必须能唯一标识每一维组合,比如按月+地区汇总,主键就得是
(year_month, region),缺一不可
最易被忽略的点:触发器内所有计算必须是确定性的,且不能依赖当前事务外的其他行状态。一旦开始想“重算全量”或“查最新总数”,就已脱离触发器适用场景——该交给应用层定时任务或物化视图了。











