触发器报错不自动回滚事务,必须显式控制:SQL Server需RAISERROR+ROLLBACK或THROW+SET XACT_ABORT ON;MySQL BEFORE触发器中SIGNAL可自动回滚但有限制;autocommit=true会使事务失效;触发器无独立事务生命周期。

触发器报错不等于事务自动回滚——必须按数据库引擎行为显式控制,否则数据状态和业务预期必然错位。
SQL Server 触发器里 RAISERROR 不会自动回滚事务
很多人写完 RAISERROR('校验失败', 16, 1) 就以为事务结束了,结果 INSERT 已经落库。这是因为 RAISERROR 只是抛错,不终止事务流。
- 必须紧跟着
ROLLBACK TRANSACTION,且要放在IF条件分支末尾,或TRY...CATCH的CATCH块里 - 如果只在
TRY块里RAISERROR而CATCH里没写ROLLBACK,事务仍挂起,可能阻塞后续操作 - 推荐改用
THROW(SQL Server 2012+),它默认中断批处理,但必须配合SET XACT_ABORT ON才能可靠中止整个事务 -
RAISERROR严重级别必须 ≥ 11 才能被客户端捕获;级别 10 及以下仅当WITH LOG才写入错误日志,且不中断执行
MySQL BEFORE 触发器失败会自动回滚主语句,但有严格限制
SIGNAL SQLSTATE '45000' 在 BEFORE 触发器中确实会让主 INSERT/UPDATE 失败并回滚,这是 InnoDB 引擎保证的原子性。但它不是万能兜底。
- 适合简单校验,比如
IF NEW.amount - 不适合需要日志记录、重试或降级的场景——
SIGNAL一出,整个语句就断了,没法补救 - 一旦触发器里发生未捕获异常(如除零、表不存在),整个语句失败,但错误信息模糊,必须立刻执行
SHOW WARNINGS才能看到真实报错 - 想记录错误又不让主事务失败?得用
DECLARE CONTINUE HANDLER FOR SQLEXCEPTION捕获并写日志表,而不是靠SIGNAL
autocommit=true 是事务失效的隐形杀手
无论 MySQL 还是 SQL Server,只要连接处于自动提交模式,BEGIN TRANSACTION 和 ROLLBACK 都是摆设。
- SQL Server 驱动(如
go-mssqldb)默认autocommit=true,必须在连接字符串里显式加autocommit=false - MySQL 默认
autocommit=1,单条UPDATE/DELETE执行完立刻提交,根本没法回滚 -
DDL语句(CREATE TABLE、ALTER TABLE)、LOCK TABLES、甚至某些SELECT FOR UPDATE都会隐式提交当前事务,让前面的START TRANSACTION失效 - 用
SHOW ENGINE INNODB STATUS查TRANSACTIONS段,能确认事务是否真正在运行,而不是被悄悄提交了
触发器没有独立事务生命周期,成败取决于上层上下文
触发器只是事务里的一个环节,它的错误传播、回滚行为最终由连接状态、错误处理方式和引擎行为共同决定。
- 不能在触发器里用
BEGIN TRANSACTION开启新事务——它运行在外部 DML 的事务上下文中 - 嵌套触发器中
@@TRANCOUNT会累加,但一次ROLLBACK TRANSACTION就能回到最外层起点,不用循环回滚 - 复杂业务逻辑(如调用外部服务、更新多张关联表)应移出触发器,放到应用层或存储过程里统一控制事务边界
- 真正容易被忽略的点在于:你写的那句
ROLLBACK是否真的被执行了——检查@@TRANCOUNT、验证日志表写入、用RAISERROR(... WITH NOWAIT)确认触发器是否运行,比纠结语法更重要











