MySQL触发器中禁止使用COMMIT或ROLLBACK,因其必须依附外层事务原子性执行;NEW/OLD仅反映显式修改字段,未更新字段的NEW值为NULL,需用NULL安全比较和COALESCE避免数据清空;复杂逻辑应前置至应用层。

触发器里不能用事务控制语句
MySQL 触发器运行在主 SQL 语句的上下文中,BEGIN...END 块里写 COMMIT 或 ROLLBACK 会直接报错:ERROR 1307 (HY000): Function or trigger cannot contain a COMMIT or ROLLBACK statement。这不是限制性能,而是设计使然——触发器必须跟随外层语句原子性提交或回滚。
常见错误是想在 AFTER INSERT 里“单独提交”冗余更新,结果整个插入失败。正确做法是把所有变更逻辑放在同一事务内,靠外层语句兜底。
- 冗余字段更新必须用
UPDATE语句,不能封装成存储过程再调用事务 - 如果需要条件跳过更新,用
IF ... THEN判断,而不是试图ROLLBACK - 涉及多表联动时,优先用
AFTER触发器(确保主表数据已落盘),避免BEFORE中读不到刚插入的自增 ID
UPDATE 触发器要小心 NEW 和 OLD 的值来源
NEW 和 OLD 是触发器里唯二能访问的行级上下文变量,但它们的值取决于触发时机和字段是否被显式修改。比如执行 UPDATE users SET name='a' WHERE id=1,哪怕 email 字段没出现在 SET 子句里,OLD.email 仍可读,但 NEW.email 就是 NULL(除非该字段有默认值或允许 NULL)。
这会导致冗余同步出错:比如你写 SET profile_summary = CONCAT(NEW.name, ' | ', NEW.email),当 email 没被更新时,NEW.email 为 NULL,整个表达式变成 NULL,把原来有效的值清空了。
- 判断字段是否被修改,用
IF NOT (NEW.email OLD.email) THEN ...(支持 NULL 安全比较) - 拼接字符串时加
COALESCE(NEW.email, OLD.email)避免 NULL 泄漏 - 不要依赖
NEW.*全量覆盖冗余字段,只同步真正变化的部分
触发器性能损耗集中在高并发 UPDATE 场景
触发器本身不锁表,但每次触发都是一次额外的 SQL 执行。问题不在单次慢,而在它会放大锁等待:比如一个 UPDATE 触发器去更新另一张表的统计字段,那这张统计表就成了热点,多个连接排队等行锁。
典型现象是监控看到 innodb_row_lock_time_avg 突增,同时 SHOW PROCESSLIST 里一堆 Updating 状态卡在触发器里的 UPDATE 语句上。
- 冗余字段尽量存在同一张表里(如
user_cache字段),避免跨表 UPDATE - 统计类字段(如“该用户订单数”)改用异步任务或物化视图替代触发器
- 触发器体内容越短越好,禁止调用自定义函数(函数内再查表极易死锁)
MySQL 8.0+ 的触发器无法引用 CTE 或窗口函数
即使你的主 SQL 用了 WITH,触发器内部依然不能写 WITH t AS (...),也不能用 ROW_NUMBER()、RANK() 这类函数——报错 ERROR 1356 (HY000): View 'xxx' references invalid table(s) or column(s)。这是语法解析阶段的硬限制,跟权限无关。
这意味着你想在触发器里“根据最新 3 条订单算平均金额”这类逻辑,必须拆成子查询或临时表模拟,但临时表在触发器里又受限(不能用 CREATE TEMPORARY TABLE)。
- 复杂计算逻辑一律前置到应用层或定时任务中
- 真要在触发器里聚合,只能用
(SELECT AVG(amount) FROM orders WHERE user_id = NEW.user_id ORDER BY created_at DESC LIMIT 3),但注意这种子查询无索引时极慢 - 如果业务允许,把“最近 N 条”转为“最近 N 小时”,用时间范围 + 索引加速
冗余字段同步看着简单,但触发器的执行边界、变量生命周期、锁行为和语法限制,每一条都容易在上线后暴露。最常被忽略的是:你以为 NEW 包含整行新值,其实它只包含 SET 子句明确赋值的字段——这点在部分更新场景下,直接导致数据静默损坏。


















