触发器执行失败会导致整个事务回滚;其运行在当前sql语句的同一事务中,内部错误(如外键冲突、类型转换失败、非法修改new)会终止语句并回滚事务,且mysql 8.0+支持get diagnostics有限捕获错误。

触发器执行失败会导致整个事务回滚
MySQL 中触发器运行在当前 SQL 语句的上下文中,属于同一事务。一旦 BEFORE 或 AFTER 触发器内部抛出错误(比如插入非法数据、调用不存在的函数、违反约束),整个语句立即终止,且所在事务会回滚——哪怕主 SQL 本身语法正确、逻辑无误。
常见错误现象包括:ERROR 1452 (HY000): Cannot add or update a child row(外键冲突)、ERROR 1292 (22007): Truncated incorrect DOUBLE value(类型隐式转换失败)、ERROR 1362 (HY000): Updating of <code>NEW row is not allowed(在 AFTER 触发器里修改 NEW)。
- 触发器里不能用
COMMIT/ROLLBACK,否则直接报错ERROR 1305 (42000): SAVEPOINT does not exist -
BEFORE INSERT中对NEW.column赋值是安全的;但若赋值后仍违反表定义(如超长字符串写入VARCHAR(10)),仍会在语句执行阶段失败 - 触发器中调用存储过程时,该过程内任何未捕获的异常也会向上冒泡,导致触发器失败
MySQL 8.0+ 支持在触发器中用 GET DIAGNOSTICS 捕获错误
早期版本(如 5.7)无法在触发器内做错误处理,只能靠外围应用兜底。MySQL 8.0.2 开始支持 GET DIAGNOSTICS,配合 DECLARE ... HANDLER 可实现有限的错误拦截。
但要注意:仅限 CONTINUE HANDLER,且不能用于跳过主 SQL 的执行——它只能帮你记录日志或设置标记变量,无法让失败的 INSERT/UPDATE 继续完成。
DELIMITER $$
CREATE TRIGGER t_before_insert_check
BEFORE INSERT ON orders
FOR EACH ROW
BEGIN
DECLARE exit_code INT DEFAULT 0;
DECLARE CONTINUE HANDLER FOR SQLEXCEPTION
BEGIN
GET DIAGNOSTICS CONDITION 1 exit_code = MYSQL_ERRNO;
INSERT INTO trigger_error_log VALUES (NOW(), 'orders insert', exit_code);
END;
IF NEW.amount < 0 THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Amount cannot be negative';
END IF;
END$$
DELIMITER ;
替代方案:用存储过程封装业务逻辑,绕开触发器限制
真正需要“失败不中断”或“失败后降级处理”的场景,硬塞进触发器只会增加不可控性。更稳妥的做法是把校验、关联更新、日志写入等逻辑移到存储过程中,由应用显式调用。
- 避免触发器嵌套:一个触发器触发另一个表的触发器,再触发第三个……容易形成死锁或堆栈溢出
- 性能影响明显:每个 INSERT/UPDATE 都强制多跑一段逻辑,尤其涉及 SELECT 查询或跨表操作时,QPS 下降显著
- 调试困难:触发器无断点、无返回值、错误堆栈不包含触发器行号,排查靠
SELECT插桩或查general_log - 上线变更风险高:ALTER TABLE 后触发器可能失效,且
mysqldump默认不导出触发器,备份还原易遗漏
生产环境建议的错误处理链路
不要指望触发器自己消化所有异常。合理分工才是关键:数据库层守住强约束(NOT NULL、CHECK、FK),弱业务规则和补偿逻辑交给应用或异步任务。
- 建表时用
CHECK约束代替触发器做简单校验(MySQL 8.0.16+ 支持) - 对必须同步执行的副作用(如更新统计表),改用应用层双写 + 最终一致性保障
- 若坚持用触发器,务必在测试库模拟各种脏数据输入,验证其是否按预期报错而非静默失败
- 监控项要覆盖
Com_trg_*状态变量(如Com_trg_insert)和错误日志中的TRIGGER关键字
触发器不是万能胶,它黏得越紧,撕下来时越容易带下一块表结构。










