什么时候该用 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),如果批量导入依赖触发器,务必验证实际行为。
