MySQL触发器是绑定表的事件驱动存储过程,由INSERT/UPDATE/DELETE自动触发,不接受参数、不可显式调用;核心特征包括BEFORE/AFTER时机、FOR EACH ROW行级执行、事务内回滚保障,但外键级联删除和TRUNCATE会绕过它,嵌套调用易引发死锁与性能雪球。

MySQL触发器是自动执行的存储过程,绑定在表上、由INSERT/UPDATE/DELETE事件隐式触发;但它不是“万能钩子”,用错会直接破坏数据一致性或拖垮性能。
触发器本质:不是函数,也不是中间件逻辑
它没有调用入口,不接受参数,不能被CALL执行,只响应行级DML事件。核心特征有三点:
-
BEFORE或AFTER决定时机:比如BEFORE INSERT可改NEW.column值,AFTER UPDATE适合写日志 - 必须指定
FOR EACH ROW:哪怕一条INSERT ... VALUES (...), (...)插入100行,触发器也执行100次 - 作用域仅限当前事务:若触发器内出错(如
INSERT INTO nonexistent_table),整个原始DML语句回滚——但这个“安全”只在单条语句内有效
外键级联删除绕过触发器是高频坑点
当父表设了ON DELETE CASCADE,子表对应行被删时,不会触发子表的DELETE触发器。现象是:你写了审计日志触发器,结果日志里根本没记录这些删操作。
- 验证方法:对子表执行
DELETE FROM child WHERE id = ?→ 日志出现;但DELETE FROM parent WHERE id = ?→ 日志为空 - 补救方案:要么把外键约束改成
ON DELETE RESTRICT,业务层显式删子表再删父表;要么在父表AFTER DELETE里手动INSERT日志 - 注意:
TRUNCATE TABLE同样绕过所有触发器,且不记binlog(ROW格式下),无法回滚
嵌套触发与死锁风险真实存在
触发器里再修改其他表,可能激活另一张表的触发器,形成链式调用。MySQL默认允许最多16层嵌套(max_sp_recursion_depth),但实际中2层就容易出问题。
- 典型死锁场景:A表
AFTER UPDATE更新B表 → B表BEFORE UPDATE又去查A表锁住的行 - 性能雪球:一个
UPDATE t1 SET x=1 WHERE y>1000影响5万行,触发器里每行都INSERT INTO log_table→ 瞬间生成5万条日志,磁盘IO和锁竞争飙升 - 调试困难:错误堆栈不显示触发器调用链,
SHOW ENGINE INNODB STATUS里只能看到最后卡住的SQL,源头难定位
事务边界模糊导致“半生效”状态
触发器代码本身在同一个事务里执行,但它的副作用(比如发消息、调外部API)无法回滚。更隐蔽的是:触发器里用START TRANSACTION会报错,而INSERT INTO t2失败只会让当前行的触发逻辑失败,不影响主DML对其他行的处理(仅限AFTER)。
- 例如:
AFTER INSERT ON orders里循环插入10条日志,第5条因唯一键冲突失败 → 前4条已写入,后5条没写,但原始订单插入成功 - 修复思路:所有关键副作用必须和主表操作在同一个
INSERT/UPDATE/DELETE语句里完成,避免分步提交 - 真正危险的是
BEFORE触发器里做复杂计算+异常退出:可能导致NEW字段被部分修改,后续逻辑读到脏中间态
触发器真正的复杂点不在语法,而在它把原本清晰的应用层事务边界,悄悄折叠进数据库内核执行路径里——一旦涉及多表联动、异步动作或错误分支,几乎必然要面对“看起来执行了,其实没生效”或“看起来没报错,其实数据已错”的情况。


















