rollback在触发器中会回滚整个宿主事务,因其无独立事务上下文,仅属宿主dml事务的一部分;执行后连原始语句、已触发逻辑及同事务内所有操作一并撤销,且sql server外多数引擎不支持局部回滚。

ROLLBACK在触发器里会终止整个宿主事务
因为触发器没有自己的事务上下文,它只是宿主 DML(如 INSERT)所处事务的一个执行环节。一旦触发器内执行 ROLLBACK TRANSACTION,数据库引擎直接回滚到该事务最外层起点——不是只撤回触发器里的操作,而是连原始那条 INSERT、之前所有同事务内的语句、甚至其他已触发的触发器逻辑,一并撤销。
-
ROLLBACK不区分“谁写的代码”,只认事务边界;宿主语句开启的事务就是它的全部作用域 - 哪怕触发器里只做了一次
SELECT后就ROLLBACK,只要该SELECT被阻塞(比如等锁),整个 INSERT 就卡住不动——这不是 bug,是事务隔离机制的必然表现 - SQL Server 中嵌套触发器会让
@@TRANCOUNT加 1,但一次ROLLBACK就清零,不会逐层退出 - MySQL 根本不支持在触发器里写
ROLLBACK,语法报错;你看到的“中断”,其实是SIGNAL导致语句失败,由引擎自动回滚整条语句
为什么不能靠ROLLBACK“局部挽救”某次失败
事务系统不提供“子事务成功、父事务失败”的混合状态。触发器里调用的存储过程、更新的关联表、甚至插入的日志记录,都和主表变更共享同一份 undo log 和事务 ID。所谓“局部回滚”,数据库层面无法实现——SAVE TRANSACTION 是唯一接近的机制,但它只能设点、不能隔离语义,且 SQL Server 外的多数引擎不支持。
-
SAVE TRANSACTION savepoint_name允许后续ROLLBACK TO savepoint_name,但该保存点仍属外层事务,无法防止其他并发事务读到中间态 - 如果触发器 A 里设了 savepoint,然后调用的触发器 B 执行了
ROLLBACK,整个事务照样崩,A 里的 savepoint 彻底失效 - 日志表写入(如
INSERT INTO audit_log)若发生在 savepoint 之后,ROLLBACK TO可撤回;但若发生在之前,它和主表更新一起被顶层ROLLBACK干掉
触发器里该用RAISERROR还是ROLLBACK
优先用 RAISERROR(SQL Server)或 SIGNAL(MySQL),而不是自己写 ROLLBACK。前者抛异常交由上层决定是否回滚,后者直接强杀整个事务流程,容易掩盖真实问题。
-
RAISERROR('invalid status', 16, 1)后不跟ROLLBACK→ 宿主INSERT可能照常提交,数据不一致 -
THROW(SQL Server 2012+)默认中断批处理,但必须配合SET XACT_ABORT ON才能确保事务终止;单独用THROW在XACT_ABORT = OFF下仍可能提交 - MySQL 的
SIGNAL SQLSTATE '45000'会强制语句失败并回滚,但仅限于 BEFORE 触发器;AFTER 触发器中不允许 SIGNAL,写了直接报语法错误 - 应用层连接若处于
autocommit=true模式(如多数 Go/Python 驱动默认),任何ROLLBACK都无效——事务根本没真正开启
真正容易被忽略的点
触发器不是独立执行单元,它没有事务生命周期,也没有错误恢复能力。你以为在触发器里“做了点事”,其实那件事根本不在事务保护范围内——比如调用 xp_cmdshell 写文件、发 HTTP 请求、甚至 PRINT 输出,这些操作一旦发出就不可逆,ROLLBACK 对它们完全无效。事务只管 T-SQL 数据变更,不管外部世界发生了什么。










