启动SQL Job失败导致外部事务回滚的问题及解决方案咨询
问题场景验证
你遇到的异常是完全可复现的合理场景:
- SQL Server启动代理作业的系统存储过程
sp_start_job(你的StartSqlJob底层大概率调用的就是这个)执行失败时(比如作业已经在运行),会主动终止并回滚当前连接所在的所有事务,同时将连接的事务计数重置为0 - 这一操作是SQL层面直接触发的,上层C#的try/catch只能捕获代码异常,无法阻止SQL层面的事务回滚行为,因此外部TransactionScope对应的事务实际已经被销毁,调用
scope.Complete()时自然会报"无对应BEGIN TRANSACTION"的错误
方案可行性说明
将SQL Job启动步骤包裹在独立事务的方案完全可行,但是更推荐的是把启动作业的逻辑完全移出核心事务,原因有二:
- 核心事务未提交时启动作业没有意义:作业需要处理的是已经持久化的核心数据,若核心事务后续回滚,提前启动的作业会读到脏数据或者做无效计算
- 独立事务可以完全隔离启动作业的异常/回滚行为,不会影响核心事务状态
重构代码示例
var opt = new TransactionOptions() { IsolationLevel = IsolationLevel.ReadCommitted, Timeout = new TimeSpan(0, this.TimeoutMin, 0) }; // 第一步:仅执行核心数据操作,和作业启动逻辑完全解耦 using (var coreScope = new TransactionScope(TransactionScopeOption.Required, opt)) { repo.Save(myObject); repo.Update(myOtherObject); coreScope.Complete(); } // 第二步:核心事务提交成功后,单独执行作业启动逻辑 try { // 使用RequiresNew开启完全独立的事务,和核心事务无关联 var jobOpt = new TransactionOptions() { IsolationLevel = IsolationLevel.ReadCommitted, Timeout = TimeSpan.FromSeconds(30) }; using (var jobScope = new TransactionScope(TransactionScopeOption.RequiresNew, jobOpt)) { repo.StartSqlJob(); jobScope.Complete(); } } catch (SqlException) { // 作业启动失败直接忽略,不影响主流程 }
如果因业务需要必须把作业启动放在原有事务中,也可以给StartSqlJob单独创建独立的数据库连接/独立的DbContext,不使用当前事务绑定的连接,也能避免核心事务被连带回滚。
内容的提问来源于stack exchange,提问作者Zoltan Hernyak
相关产品推荐
相关产品推荐

