MySQL触发器不是独立事务,必须依附主DML语句的隐式事务,所有操作自动加入该事务;显式或隐式COMMIT、ROLLBACK等均被内核级禁止,报ERROR 1422,正确做法是用SIGNAL抛异常以中止并回滚整个事务。

触发器根本不是独立事务,它必须依附主DML语句
MySQL 触发器没有自己的事务上下文,它运行在 INSERT、UPDATE 或 DELETE 语句所开启的隐式事务中。所有触发器内的 SQL 操作(比如往日志表 INSERT)自动加入该事务——主语句成功则一起提交,主语句失败(如被 SIGNAL 中止)则全部回滚。你写 COMMIT,等于强行把一部分变更“钉死”,后续若主语句出错,数据库就处于不一致状态。
ERROR 1422 不是偶然报错,而是解析阶段就被拦截
只要触发器或它调用的存储过程中存在 COMMIT、ROLLBACK、START TRANSACTION,甚至只是语法上写了(哪怕包在 IF FALSE THEN COMMIT; END IF; 里),MySQL 在解析触发器定义时就会拒绝创建,报 ERROR 1422: Explicit or implicit commit is not allowed in stored function or trigger。这不是执行时报错,所以堆栈里看不到具体哪一行,只提示上下文违规。
-
CREATE PROCEDURE p() BEGIN COMMIT; END单独执行没问题,但一旦被触发器CALL p(),建触发器就失败 -
TRUNCATE TABLE、ALTER TABLE、对MyISAM表操作,都会隐式提交,同样触发ERROR 1422 -
SAVEPOINT是唯一允许的事务控制语句,但只能用于局部回滚,不能替代COMMIT
想校验失败就终止整个操作?别用 ROLLBACK,改用 SIGNAL
很多人在触发器里写 ROLLBACK 想“只撤回触发器动作”,结果报 ERROR 1305 (42000): FUNCTION does not exist——这其实是 MySQL 拦截了非法语句,不是函数缺失。正确做法是抛异常中断父事务:
IF NEW.amount < 0 THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Amount cannot be negative'; END IF;
这样主 INSERT 会失败,整个事务由 MySQL 自动回滚,无需你干预。
哪些操作看似安全,实则一碰就崩?
最容易被忽略的是“间接调用”:触发器没写任何事务语句,但 CALL 的存储过程里有 START TRANSACTION,照样崩。还有:
- 在触发器中
SELECT ... FOR UPDATE后再更新另一张表,可能引发锁等待超时,但错误堆栈不体现根源 - 对同一张表做 DML(比如
AFTER INSERT ON t里又UPDATE t),直接报ERROR 1442 - 跨库写日志没带库名,如
INSERT INTO audit_log实际去当前库找,找不到就静默失败
真正安全的触发器操作,仅限轻量级、无副作用、不越界的 DML:比如往独立的 InnoDB 审计表插一条记录,且不涉及事务控制、不查自身表、不调用含隐式提交的封装逻辑。


















