raiserror不会自动回滚事务,必须显式执行rollback transaction;推荐改用throw配合set xact_abort on以确保事务可靠终止。

SQL Server 触发器里 RAISERROR 不会自动回滚事务
写了 RAISERROR('库存不足', 16, 1),但数据还是插进去了——这不是 bug,是设计行为。RAISERROR 只抛错误,不终止事务执行流。后续语句(比如 RETURN 或隐式 COMMIT)仍可能跑完。
- 必须在
RAISERROR后紧跟ROLLBACK TRANSACTION,且确保它一定会执行(例如放在IF分支末尾,或TRY...CATCH的CATCH块内) - 严重级必须 ≥ 11 才能被客户端捕获;级别 10 及以下默认不中断执行,也难被应用层感知
-
@@TRANCOUNT在嵌套触发器中会累加,但一次ROLLBACK TRANSACTION就能清零到最外层起点,不用循环回滚 - 别在
TRY块里只RAISERROR不ROLLBACK——事务挂起,可能阻塞其他会话
MySQL BEFORE 触发器 SIGNAL 失效的常见原因
SIGNAL SQLSTATE '45000' 看似万能,但常静默失效。根本原因不是语法错,而是连接没处在事务上下文中。
- 检查
SELECT @@autocommit:若返回1,单条INSERT就是独立事务,SIGNAL只让它自己失败,没有“回滚”可言 - 检查
SELECT @@sql_mode:若缺失STRICT_TRANS_TABLES,字段类型不匹配、NOT NULL插NULL会被静默修正,触发器逻辑根本没机会运行 -
SIGNAL只在BEFORE触发器中有效;AFTER里再SIGNAL已无法撤回主语句变更 - 执行完 DML 后立刻
SHOW WARNINGS,否则触发器内部错误会被吞掉,看不到真实报错
应用层没捕获异常导致事务状态错乱
触发器抛了 THROW 或约束冲突,但 HTTP 接口返回 200 或超时——这通常不是数据库问题,是应用没处理好异常透传。
- Spring 中出现
UnexpectedRollbackException,往往是因为触发器已执行ROLLBACK,但 Spring 在commit()阶段才发现事务已被数据库硬终结,只能包装成这个异常 - Go/Python 等语言驱动若未显式设置
autocommit=false(如go-mssqldb默认就是true),连接一建立就自动提交,BEGIN TRANSACTION形同虚设 - ORM 框架(如 Django、SQLAlchemy)可能把原始数据库错误转成空响应或日志静默,需查底层驱动日志确认是否收到
SQLException - DDL 语句(
ALTER TABLE)、LOCK TABLES、甚至某些SELECT ... FOR UPDATE会隐式提交当前事务,让前面的START TRANSACTION失效
真正决定回滚成败的是连接状态和事务边界
触发器本身没有独立事务生命周期。它的成败,最终由三件事共同决定:当前连接是否关闭自动提交、主语句是否包裹在显式事务中、错误是否被上层正确拦截。
- MySQL 必须用
START TRANSACTION显式开启,不能只靠SET autocommit=0(该设置仅对当前会话临时有效) - SQL Server 必须配
SET XACT_ABORT ON才能让THROW可靠中断整个批处理;单独用THROW而不设此选项,约束冲突等错误仍可能漏过 - 用
SELECT @@in_transaction(MySQL)或SELECT @@TRANCOUNT(SQL Server)验证事务是否真正在运行,而不是被悄悄提交了 - 复杂业务逻辑(调用外部服务、更新多张表)不该塞进触发器——它不是事务控制器,只是校验钩子











