SQL触发器是特定约束场景下的窄口径工具,仅适用于BEFORE INSERT强制填充字段、BEFORE UPDATE校验状态流转、AFTER DELETE防误删三类必须与主DML原子执行且无法绕过的场景。

SQL触发器不是“更强的一致性方案”,而是特定约束场景下的窄口径工具;绝大多数业务逻辑应放在应用层,除非你明确需要数据库事务原子性兜底且能100%控制所有写入路径。
哪些操作必须用触发器?
仅限三类无法被绕过、且必须与主DML原子执行的场景:
-
BEFORE INSERT强制填充created_at或created_by,前提是应用层禁止传入这些字段 -
BEFORE UPDATE校验状态流转(如订单从'pending'→'confirmed'),且不允许跳变或回退 -
AFTER DELETE防误删校验(如检查关联子表记录是否存在),失败时用SIGNAL SQLSTATE '45000'中断事务
注意:MySQL 触发器不能跨库,也不能调用存储过程以外的外部服务;SHOW TRIGGERS 才能查到定义,mysqldump 默认不导出,CI/CD 容易漏掉。
为什么应用层逻辑更可控?
因为你能真正掌控执行上下文、可观测性和错误处理粒度:
- 日志、OpenTelemetry 链路追踪、慢SQL监控全链路可见;触发器只在
SHOW PROCESSLIST里显示Executing trigger,不标名称、不显SQL - 可对重试、降级、补偿任务做完整设计;触发器内抛错就整条语句失败,
ERROR 1422这类报错不指明具体哪一行 - ORM 的
@event.listens_for或 service 方法能统一拦截所有写入;但 raw SQL 绕过 ORM 就等于绕过全部应用层逻辑
关键前提:所有数据库变更必须包裹在显式事务中,且不能在 COMMIT 前发 MQ 或调外部 API。
并发和性能上最容易踩的坑
触发器不是“自动加锁”,它只是普通事务的一部分,锁行为完全由你写的SQL决定:
-
SELECT SUM(amount) FROM order_items WHERE order_id = NEW.order_id不加FOR UPDATE→ 并发更新导致汇总值丢失 -
BEFORE UPDATE里查大表没走索引 → 全表扫描 + 行锁阻塞其他写入 - 批量插入
INSERT INTO t SELECT ... LIMIT 10000→ 触发器按行执行 10000 次,CPU 和锁竞争陡增
空触发器本身就有 0.1–0.3ms 固定开销;含 SELECT ... FOR UPDATE 的,5ms 就能让锁等待翻倍——这类问题不会出现在慢查询日志里,只反映在 innodb_row_lock_waits 上。
混合使用时最隐蔽的问题
触发器改了数据,应用层却还在用旧值判断,这是典型竞态,极难复现:
- 应用读取一条订单记录后准备发通知,同时
BEFORE UPDATE触发器悄悄把status从'pending'改成'validated' - 应用仍按原始值继续执行,结果发了错误状态的通知
- 规避方式只有两个:
SELECT ... FOR UPDATE重读最新数据,或禁止触发器修改应用层依赖的关键字段
真正强一致的系统,靠的是单点写入入口 + 显式约束,而不是堆砌触发器。数据库该专注持久化、隔离、崩溃恢复,而不是替业务做决策。

















