MySQL触发器适用于强一致性、跨表约束和不可绕过的审计日志场景;应避免用于异步通知、外部API调用或复杂业务规则。BEFORE可修改NEW行,AFTER仅可读取;需警惕性能瓶颈与调试陷阱。

什么时候该用 MySQL 触发器而不是应用层逻辑
触发器不是“自动执行的魔法”,它适合解决强一致性、跨表约束、审计日志写入不可绕过这几类问题。比如订单状态变更时必须同步更新库存,且不允许任何应用代码跳过该逻辑;又比如所有对 users 表的 UPDATE 都要记录操作人、时间、旧值,哪怕调用方是 DBA 直连执行的 SQL。
反例:用触发器做异步通知(如发邮件)、调用外部 API、或处理复杂业务规则(比如积分计算涉及多张表+缓存刷新)——这些场景会拖慢事务、增加死锁风险、难以调试。
- ✅ 适用:
AFTER INSERT记录操作日志到只读审计表 - ✅ 适用:
BEFORE UPDATE校验字段组合合法性(如status = 'shipped'时shipping_time必须非空) - ❌ 避免:
AFTER UPDATE中调用SELECT ... FOR UPDATE锁其他行 - ❌ 避免:在触发器里写
INSERT INTO ... SELECT ... FROM large_table
BEFORE 和 AFTER 触发器的关键行为差异
BEFORE 触发器能修改即将插入/更新的行(通过 NEW.column_name),而 AFTER 只能读取最终结果(OLD 和 NEW 都只读)。这意味着:校验、默认值填充、字段转换必须用 BEFORE;日志归档、统计汇总、跨表联动通常用 AFTER。
一个典型陷阱是误在 AFTER 中试图修改 NEW —— MySQL 会直接报错 ERROR 1362 (HY000): Updating of NEW row is not allowed in after trigger。
-
BEFORE INSERT:可设NEW.created_at = NOW(),但不能读NEW.id(自增 ID 尚未生成) -
AFTER INSERT:可安全读NEW.id,用于写入关联日志表 - 同一张表上多个触发器按定义顺序执行,但
BEFORE和AFTER不会混序
性能隐患与规避方式
触发器运行在事务内,每行变更都触发一次,容易成为写入瓶颈。尤其当触发器中含 SELECT 查询(尤其是无索引字段)、或写入另一张大表时,响应时间可能从毫秒级升至秒级。
优化核心原则:**尽可能轻量、避免锁、不查非必要数据**。
- 禁用全表扫描:
SELECT COUNT(*) FROM audit_log WHERE table_name = 'orders'→ 改为带索引的WHERE table_name = 'orders' AND created_at > DATE_SUB(NOW(), INTERVAL 1 DAY) - 写日志优先用
INSERT DELAYED(MySQL 5.6+ 已移除)或改用应用层异步队列,触发器只写轻量标记 - 避免在触发器中调用存储函数,除非该函数是
DETERMINISTIC且无副作用 - 用
SHOW TRIGGERS LIKE 'orders'检查是否意外存在重复触发器
调试与线上禁用触发器的实操方式
触发器出错会导致整个 DML 失败,错误信息往往只显示 ERROR 1422 (HY000): Explicit or implicit commit is not allowed in stored function or trigger 这类泛化提示,实际原因可能是触发器里用了 CREATE TEMPORARY TABLE 或隐式提交语句。
上线前务必在测试库用真实流量压测,并开启 general_log 抓取触发器执行上下文。
- 临时禁用触发器(MySQL 5.7.2+):
DROP TRIGGER IF EXISTS orders_after_insert_audit;
,而不是注释 SQL - 查看触发器定义:
SHOW CREATE TRIGGER orders_before_update_status\G
- 模拟触发器逻辑做单元测试:把触发器体复制成存储过程,传入
NEW模拟数据手动执行
最常被忽略的是:触发器不作用于 LOAD DATA INFILE 和 REPLACE INTO 的部分场景(取决于 MySQL 版本和 SQL_MODE),如果批量导入依赖触发器,务必验证实际行为。










