mysql触发器禁止使用commit、rollback等事务控制语句,须用signal抛异常让外层回滚;new/old伪记录依触发时机和版本有读写限制;性能开销大,需索引优化;跨库操作需显式授权;优先用check约束替代。

触发器里不能用事务控制语句
MySQL触发器内部不允许出现 BEGIN...END 块里的 COMMIT、ROLLBACK 或显式 START TRANSACTION,否则直接报错 ERROR 1305 (42000): FUNCTION does not exist(实际是语法拦截,不是函数缺失)。这是因为触发器运行在父语句的事务上下文中,它自己不能开启/结束事务。
常见错误场景:想在校验失败时“回滚当前操作”,于是写 ROLLBACK —— 这会失败。正确做法是抛出异常中断执行,让外层语句自动回滚。
- 用
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '业务规则不满足'主动报错 - 避免在触发器中调用含事务逻辑的存储过程(除非该过程声明为
READS SQL DATA且不含事务语句) - 注意 MySQL 5.7+ 才支持
SIGNAL;5.6 及更早版本只能靠构造非法语句(如INSERT INTO nonexistent_table VALUES())来间接中断,但不可靠、难维护
NEW 和 OLD 在不同触发时机下的可用性
NEW 和 OLD 是触发器里访问行数据的关键伪记录,但它们是否可用、是否可修改,完全取决于触发器类型和 MySQL 版本。
比如 BEFORE INSERT 中只有 NEW 可读可写(可用于默认值填充或字段改写),而 AFTER DELETE 中只有 OLD 可读(但不可写);AFTER UPDATE 中两者都只读。
-
BEFORE UPDATE:可修改NEW.column_name,影响即将写入的值;OLD只读 -
AFTER INSERT:NEW只读(主键自增后已确定,不能再改) - 对
JSON字段或生成列做校验时,注意NEW中的值已是表达式计算后的结果,不是原始输入 - 如果触发器里误对
OLD赋值(如SET OLD.status = 'archived'),MySQL 会静默忽略,不报错也不生效——这是最难排查的坑之一
触发器性能开销比预想中大得多
每行变更都会同步执行触发器逻辑,哪怕只是几行 IF 判断 + SELECT 查询,也可能成为批量导入或高并发更新的瓶颈。
典型问题:在 BEFORE INSERT 里查另一张表做唯一性校验,没加索引 → 每次插入都触发全表扫描。线上曾有案例:单条 INSERT 从 2ms 涨到 380ms,只因触发器里一条没走索引的 SELECT COUNT(*)。
- 所有
SELECT必须确保命中索引,用EXPLAIN验证执行计划 - 避免在触发器里调用含复杂逻辑的存储函数,尤其是那些内部再查表或循环的
- 批量操作(如
INSERT ... SELECT或LOAD DATA)会逐行触发,此时触发器开销是线性放大的,远高于单条语句 - MySQL 8.0+ 支持将部分校验下推到 CHECK 约束,比触发器轻量得多,优先考虑
跨库操作和权限容易被忽略
触发器定义在某个数据库下,但里面引用的表可以是其他库的(如 other_db.users),这时不仅需要触发器所在库的 TRIGGER 权限,还必须有目标库表的 SELECT / UPDATE 权限——而且这些权限得是显式授予的,不能靠 GRANT ALL ON *.* 自动覆盖(MySQL 权限系统对此有限制)。
现象:触发器创建成功,但执行时突然报 ERROR 1142 (42000): SELECT command denied to user,查半天发现是跨库权限漏了。
- 用
SHOW GRANTS FOR 'user'@'host'确认是否包含类似GRANT SELECT ON `other_db`.`config` TO 'user'@'host' - 不要在触发器里用
USE other_db切库——语法不支持,会直接报错 - MySQL 5.7 默认关闭
log_bin_trust_function_creators,跨库触发器若涉及函数,可能被复制环境拒绝执行,需协调 DBA 开启











