mysql触发器依附于外部事务,不可开启新事务;before可修改new并提前终止语句,after无法修改但失败仍导致整体回滚;禁止使用start transaction/commit/rollback;跨表操作易引发死锁。

触发器自动加入当前事务
MySQL 触发器本身不开启新事务,而是**完全依附于触发它的 SQL 语句所处的事务**。也就是说,INSERT、UPDATE 或 DELETE 如果在显式事务中执行(比如包裹在 BEGIN / COMMIT 里),那么该语句激活的所有触发器逻辑都会被包含在这个事务里;如果语句是自动提交模式下的单条执行,那触发器也就在那个隐式事务中运行。
这意味着:触发器内抛出异常(比如通过 SIGNAL)、或触发器里的语句失败(如违反外键、唯一约束),都会导致整个外部事务回滚——不只是触发器代码,还包括原始 DML 语句本身。
不能在触发器里用 START TRANSACTION / COMMIT / ROLLBACK
MySQL 明确禁止在触发器中执行事务控制语句:START TRANSACTION、COMMIT、ROLLBACK、SAVEPOINT 都会直接报错 ERROR 1305 (42000): SAVEPOINT does not exist 或类似提示(实际错误码可能是 1305 或 1295)。
这是硬性限制,不是配置问题。原因在于触发器必须保持事务上下文透明,否则会破坏原子性保证。
- 想“局部回滚”某段逻辑?不行——只能靠提前校验或用
IF+LEAVE控制流程 - 想记录日志但不影响主事务?得用
INSERT ... ON DUPLICATE KEY UPDATE或写入非事务引擎表(如MyISAM),但后者已不推荐 - 依赖
SELECT ... FOR UPDATE加锁?可以,只要锁范围合理,它会和主事务一起提交或回滚
BEFORE 和 AFTER 触发器对事务行为的影响差异
BEFORE 触发器能修改即将插入/更新的行(通过 NEW.col = ...),且如果它出错(比如 SIGNAL 报错),原始语句根本不会执行;而 AFTER 触发器无法修改原数据(NEW 只读),但它执行时原始语句已经成功(至少逻辑上完成),所以它的失败会导致整个事务回滚——包括刚成功的那条 DML。
典型陷阱:
- 在
AFTER INSERT里调用存储过程写审计日志,结果日志表字段长度不够 → 整个INSERT失败 - 在
BEFORE UPDATE里做复杂计算并赋值给NEW.price,但计算过程除零 → 更新直接被拦住,不进表 - 误以为
AFTER是“事后补救”,其实它和BEFORE同样具有事务强一致性
跨表操作与死锁风险升高
触发器常用来同步其他表(比如订单变更后更新用户积分)。这类操作会隐式增加锁持有范围和时间,尤其当触发器里有 UPDATE 或 SELECT ... FOR UPDATE 时,极易引发死锁。
例如:UPDATE orders SET status = 'shipped' WHERE id = 123 激活一个 AFTER UPDATE 触发器去 UPDATE users SET points = points + 100 WHERE id = 567。如果另一事务正按相反顺序访问 users 再访问 orders,就可能卡住。
应对建议:
- 尽量避免在触发器中做多表更新,优先考虑应用层异步处理
- 确保所有跨表操作遵循固定顺序(比如总是先锁
users再锁orders) - 监控
SHOW ENGINE INNODB STATUS中的LATEST DETECTED DEADLOCK区域 - 测试时用
SELECT SLEEP(1)模拟延迟,更容易复现竞争条件
INSERT,背后可能牵扯四张表加锁、三次校验、两次信号抛出,任何一环出问题都会让整个事务倒退。










