MySQL 5.7+中BEFORE UPDATE触发器只能修改NEW值,禁止更新原表;跨表更新须用单值子查询;INSERT IGNORE/REPLACE跳过行时不触发触发器;触发器无独立事务,不可COMMIT/ROLLBACK。

BEFORE UPDATE 中只能改 NEW,不能碰原表
MySQL 5.7+ 明确禁止在触发器里对触发它的那张表执行 UPDATE/INSERT/DELETE。比如你在 t1 上建了 AFTER UPDATE 触发器,里面写 UPDATE t1 SET x = 1,会直接报错:Can't update table 't1' in stored function/trigger because it is already used by statement which invoked this stored function/trigger.
唯一安全的“改原表”方式,是在 BEFORE UPDATE 里赋值给 NEW.xxx:
-
SET NEW.updated_at = NOW();—— 合法,影响即将写入的行 -
SET NEW.status = 'pending';—— 合法,覆盖应用层传入的值 -
UPDATE t1 SET counter = counter + 1 WHERE id = NEW.id;—— 非法,哪怕加了 WHERE 也不行
想自动维护计数器、时间戳、校验字段?必须用 BEFORE + NEW,别硬刚。
跨表更新必须用标量子查询,不能 JOIN
触发器里不支持 UPDATE t2 JOIN t1 ON ... SET t2.x = t1.y 这类语法,会报错,错误信息还容易误导(比如提示子查询不支持 LIMIT,其实根本没到那步)。
正确写法是把关联逻辑收进括号里,确保返回单值:
SET NEW.sync_status = ( SELECT IF(t2.is_valid = 1, 'ok', 'error') FROM config t2 WHERE t2.key_name = NEW.config_key LIMIT 1 );
注意几个硬性要求:
- 子查询必须加
LIMIT 1,否则多行返回直接中断触发器执行 - 外键或唯一索引要建好,不能靠
LIMIT掩盖数据模型缺陷 - 这个子查询在每行触发时都执行一次,高并发写入时容易成性能瓶颈
- 如果关联表没命中(
NULL),NEW.sync_status就变成NULL,得提前考虑默认值
INSERT IGNORE / REPLACE 不触发触发器
这是最容易被忽略的坑:INSERT IGNORE 和 REPLACE INTO 对于因唯一键冲突而跳过的行,**完全不会执行任何触发器**——BEFORE INSERT 和 AFTER INSERT 都不走。
后果很实际:
- 你指望靠触发器同步冗余字段?跳过的那几行就永远不同步
- 想记录插入日志?日志表里压根没这条记录
- 用
REPLACE做“upsert”,它实际是先DELETE再INSERT,只触发BEFORE DELETE和BEFORE INSERT,但中间那条记录的状态已经丢失
稳妥方案只有两个:
- 统一用
INSERT ... ON DUPLICATE KEY UPDATE,它一定触发BEFORE INSERT,MySQL 8.0.19+ 还能触发AFTER INSERT - 把兜底逻辑移到应用层,靠 SQL 返回的
affected_rows判断是否真插入成功,再手动补同步
触发器没有事务边界,也不能 COMMIT/ROLLBACK
触发器不是独立事务,它和调用它的 SQL 共享同一个事务上下文。也就是说:
- 触发器里写的更新,会随主 SQL 一起提交或回滚
- 你不能在触发器里写
COMMIT或ROLLBACK,会报错:Explicit or implicit commit is not allowed in stored function or trigger - 如果触发器里的子查询失败(比如关联表被锁、超时),整个主 SQL 会失败并回滚
这意味着:一旦用了触发器做跨表联动,你就得接受“强一致性 + 强耦合”。某张关联表不可用,主表写入就失败。生产环境若要求高可用,得评估这种耦合是否可接受——很多时候,异步消息或定时补偿更合适。


















