必须同时监听insert和update操作,因新插入sku或价格更新均可能影响最低价;需在触发器中显式过滤status = 'active',并针对new.sku_id单点重算,避免join导致脏读或全表扫描。

触发器该监听哪些表和操作
SKU最低价格变动必须响应 price 字段的更新,且只关心已上架商品(status = 'active')。不能只监听 UPDATE,因为新插入的 SKU 也可能成为当前最低价——所以必须同时响应 INSERT 和 UPDATE。如果业务允许下架后价格仍参与计算,那还要考虑 DELETE 或状态变更;但通常下架商品应被排除,因此触发器里必须显式过滤 status = 'active'。
如何避免触发器递归修改自身导致死循环
常见错误是:在更新 sku_summary 表时,又触发了该表的 UPDATE 触发器,进而无限嵌套。解决方案只有两个:
• 在触发器内用 pg_trigger_depth()(PostgreSQL)或 @@NESTLEVEL(SQL Server)判断是否已处于触发上下文,是则直接 RETURN;
• 更稳妥的做法是把聚合逻辑移出触发器,改用 MATERIALIZED VIEW(PG)或 INDEXED VIEW(SQL Server),但这就不是“自动维护”而是“定时刷新”了;
• MySQL 用户注意:innodb_lock_wait_timeout 可能掩盖死锁,务必在触发器开头加 IF NOT EXISTS (SELECT 1 FROM information_schema.triggers WHERE trigger_name = 'trg_sku_price_update') THEN ... END IF; 类似防护不现实,老老实实加深度检查。
聚合计算必须用子查询而非 JOIN 防止数据错位
错误写法:UPDATE sku_summary s JOIN (SELECT sku_id, MIN(price) AS min_p FROM products WHERE status='active' GROUP BY sku_id) p ON s.sku_id = p.sku_id SET s.min_price = p.min_p —— 这在并发更新时可能读到脏数据,且无法限定只更新本次涉及的 SKU。正确做法是:触发器内只针对刚变更的那行(NEW.sku_id)做单点重算:
• SELECT MIN(price) INTO v_new_min FROM products WHERE sku_id = NEW.sku_id AND status = 'active'
• 再 UPDATE sku_summary SET min_price = v_new_min WHERE sku_id = NEW.sku_id
• 如果 sku_summary 表还没这行,得先 INSERT ... ON CONFLICT DO UPDATE(PG)或 MERGE(SQL Server)
MySQL 8.0+ 的 BEFORE 触发器不能修改 NEW 行以外的数据
MySQL 的 BEFORE INSERT/UPDATE 触发器只能改 NEW.* 字段,不能执行 UPDATE sku_summary ...。必须改用 AFTER 触发器。但 AFTER 有风险:如果事务回滚,触发器已执行的 UPDATE 不会自动回滚(除非你手动写 ROLLBACK 逻辑,但 MySQL 不允许在触发器里调 ROLLBACK)。所以实际部署时:
• 优先用存储过程封装“更新 product + 同步 summary”的原子操作
• 或接受最终一致性,用消息队列异步更新 summary 表
• 硬要用触发器?确保应用层绝不在同一事务中混用 DML 和 DDL,否则 AFTER 触发器可能被跳过
真正难的不是写触发器,而是确认「最低价」的业务定义是否包含促销价、会员价、区域价这些衍生字段;一旦定义变,所有触发器逻辑都要重审——而这类变更往往不会通知 DBA。










