该用after:积分变动依赖业务逻辑判断(如订单支付成功才加积分),before易因old/new未反映最终值或修改new干扰主逻辑导致失真;after确保数据已提交,可安全调用函数计算积分并写入日志。

触发器该用 BEFORE 还是 AFTER?
关键看你要记录的是“变更前状态”还是“变更后结果”。积分变动通常依赖业务逻辑判断(比如订单支付成功才加积分),所以绝大多数场景应该用 AFTER INSERT 或 AFTER UPDATE。用 BEFORE 容易出问题:比如在 BEFORE UPDATE 里读取 OLD.score 和 NEW.score 看似能算差值,但若原表字段本身是通过计算列或触发器链式更新的,OLD/NEW 可能还没反映最终值;更麻烦的是,BEFORE 中修改 NEW 字段会干扰主表逻辑,反而让积分记录失真。
实操建议:
- 订单表
orders插入一条status = 'paid'记录后,再往积分流水表score_logs写一条 +100 的记录 → 用AFTER INSERT - 用户手动调用存储过程调整积分,且该过程已确保数据一致性 → 触发器挂载在目标表的
AFTER UPDATE上更安全 - 避免在触发器里再更新同一张表(比如在
users的AFTER UPDATE里又UPDATE users),会报错或死锁
怎么拿到准确的增减数值?别硬写固定值
硬编码 +50 或 -20 看似简单,但一旦规则变(比如 VIP 用户下单双倍积分),就得改所有触发器。真正可维护的做法是把计算逻辑收口到函数或视图里。
常见错误现象:触发器里直接写 INSERT INTO score_logs VALUES (..., 50, ...),后续运营提需求要按商品类目翻倍,开发得翻出全部触发器逐个改。
实操建议:
- 建一个确定性函数
calc_order_score(order_id INT),内部查orders、products、user_vips表返回应积分数,触发器里只调它:calc_order_score(NEW.id) - 函数必须标为
DETERMINISTIC(MySQL)或STABLE(PostgreSQL),否则某些数据库禁止在触发器中调用 - 如果计算涉及多表 JOIN 且性能敏感,考虑把关键字段冗余进主表(比如在
orders加score_rule_code字段),避免触发器执行时查表超时
如何防止重复记录和事务不一致?
最典型的问题是:应用层开启事务插入订单,触发器写积分日志,但主事务回滚了,日志却没删——因为 MySQL 的 AFTER 触发器默认在事务内执行,但 PostgreSQL 的 AFTER 触发器实际在事务提交后才运行(除非显式声明 FOR EACH ROW EXECUTE FUNCTION ... 并配合 TRANSITION TABLE)。这会导致跨库迁移时行为不一致。
实操建议:
- MySQL 下无需额外处理,只要主事务失败,触发器写入自动回滚
- PostgreSQL 必须用
AFTER INSERT ... FOR EACH ROW+ 普通触发器函数(非TRANSITION),它参与主事务;若要用TRANSITION TABLE做批量处理,则需确认应用层事务已提交 - 无论哪种数据库,
score_logs表的user_id、ref_id(如订单ID)、ref_type要建唯一索引:UNIQUE (user_id, ref_id, ref_type),靠数据库兜底防重
为什么日志表不能用 MyISAM? MyISAM 不支持事务,而触发器写日志的动作必须和主表操作原子性绑定。哪怕只是想“快速写入”,选 MyISAM 也会导致主表回滚时日志残留——这不是性能问题,是数据一致性灾难。
实操建议:
- MySQL 必须用
InnoDB引擎,且score_logs表显式指定ENGINE=InnoDB - PostgreSQL 不用担心引擎选择,但要注意
score_logs表不要加UNLOGGED属性,否则崩溃恢复时日志丢失 - 如果日志量极大(日增千万级),考虑按月分表(如
score_logs_202404),但触发器本身不能动态切表名,得靠应用层路由或使用分区表(MySQL 8.0+ / PG 10+)
DETERMINISTIC 标记的函数,或一张 MyISAM 日志表,就可能让积分对不上账——这种问题在线上往往要查好几天。










