包含TRY CATCH ROLLBACK模式的嵌套存储过程?

我对以下模式的副作用和潜在问题感兴趣:

CREATE PROCEDURE [Name] AS BEGIN BEGIN TRANSACTION BEGIN TRY [...Perform work, call nested procedures...] END TRY BEGIN CATCH ROLLBACK TRANSACTION RAISERROR [rethrow caught error using @ErrorNumber, @ErrorMessage, etc] END CATCH END 

据我所知,这种模式在使用单个过程时是合理的 – 过程将完成所有的语句而没有错误,或者它将回滚所有的操作并报告错误。

但是,当一个存储过程调用另一个存储过程来完成某个子单元的工作时(我们理解小过程有时自己会被调用),所以我看到一个与回滚有关的问题 – 一个信息性消息(级别16)发出说明The ROLLBACK TRANSACTION request has no corresponding BEGIN TRANSACTION. 。 我假设这是因为子过程中的回滚总是回滚最外层的事务,而不仅仅是在子过程中启动的事务。

我希望整个事情回滚和中止,如果有任何错误发生(和错误报告给客户端作为SQL错误),我只是不确定所有来自外层尝试回滚事务的副作用已经回滚了。 在每个TRY CATCH层执行回滚之前,可能需要检查@@TRANCOUNT

最后是客户端(Linq2SQL),它有自己的事务层:

 try { var context = new MyDataContext(); using (var transaction = new TransactionScope()) { // Some Linq stuff context.SubmitChanges(); context.MyStoredProcedure(); transactionComplete(); } } catch { // An error occured! } 

如果在MyStoredProcedure 内部调用的存储过程“MySubProcedure”引发错误,我能确定以前在MyStoredProcedure中所做的所有事情都将被回滚,所有由SubmitChanges所做的Linq操作将被回滚,最后错误将被记录? 或者我需要改变我的模式,以确保整个操作是原子性的,同时仍然允许单独使用子部分(即子程序应该仍然具有相同的原子保护)

这是我们的模板(错误记录被删除)

这是为了处理

  • Paul Randal的文章“在SQL Server中不存在嵌套事务”
  • 错误266
  • 触发回滚

说明:

  • 所有的TXN开始和提交/回滚必须配对,使@@TRANCOUNT是相同的进入和退出

  • @@TRANCOUNT不匹配导致错误266,因为

    • BEGIN TRAN递增@@TRANCOUNT

    • COMMIT递减@@TRANCOUNT

    • ROLLBACK@@TRANCOUNT返回到零

  • 您不能为当前范围递减@@TRANCOUNT
    这就是你认为的“内部交易”

  • SET XACT_ABORT ON禁止由于@@TRANCOUNT不匹配导致的错误266
    还处理dba.se上的这个“SQL Server Transaction Timeout”问题

  • 这允许客户端TXN(如LINQ)单个存储过程可能是分布式或XA事务的一部分,或者简单地以客户端代码(例如.net TransactionScope)启动,

用法:

  • 每个存储的proc必须符合相同的模板

概要

  • 所以不要创造比你需要更多的TXN

代码

 CREATE PROCEDURE [Name] AS SET XACT_ABORT, NOCOUNT ON DECLARE @starttrancount int BEGIN TRY SELECT @starttrancount = @@TRANCOUNT IF @starttrancount = 0 BEGIN TRANSACTION [...Perform work, call nested procedures...] IF @starttrancount = 0 COMMIT TRANSACTION END TRY BEGIN CATCH IF XACT_STATE() <> 0 AND @starttrancount = 0 ROLLBACK TRANSACTION; THROW; --before SQL Server 2012 use --RAISERROR [rethrow caught error using @ErrorNumber, @ErrorMessage, etc] END CATCH GO 

笔记:

  • 回滚检查实际上是多余的,因为SET XACT_ABORT ON 。 但是,这让我感觉更好,看起来很奇怪,并且允许你不想要的情况

  • Remus Rusanu有一个使用保存点的类似shell 。 我更喜欢原子数据库调用,不要像使用文章那样使用部分更新

我不是一个Linq家伙(也不是Erland),但他写错误处理的绝对圣经。 除了Linq可能给您的问题增加的复杂性之外,您的所有其他问题都应该在这里回答:

http://www.sommarskog.se/error_handling/Part1.html

(旧链接: http : //www.sommarskog.se/error_handling_2005.html )

为了解决返回@AlexKuznetsov提到的错误号和行号的问题,可以这样提出错误:

 DECLARE @ErrorMessage NVARCHAR(4000) DECLARE @ErrorSeverity INT DECLARE @ErrorState INT DECLARE @ErrorLine INT DECLARE @ErrorNumber INT SELECT @ErrorMessage = ERROR_MESSAGE(), @ErrorSeverity = ERROR_SEVERITY(), @ErrorState = ERROR_STATE(), @ErrorNumber = ERROR_NUMBER(), @ErrorLine = ERROR_LINE() RAISERROR (@ErrorMessage, @ErrorSeverity, @ErrorState, @ErrorNumber, @ErrorLine) 

– 上面的@Amanda方法不会返回正确的错误号

 DECLARE @ErrorMessage nvarchar(4000), @ErrorSeverity int, @ErrorState int, @ErrorLine int, @ErrorNumber int BEGIN TRY SELECT 1/0; -- CATCH me END TRY BEGIN CATCH DECLARE @err int = @@ERROR PRINT @err -- 8134, divide by zero PRINT ERROR_NUMBER() -- 8134 SELECT @ErrorMessage = ERROR_MESSAGE(), @ErrorSeverity = ERROR_SEVERITY(), @ErrorState = ERROR_STATE(), @ErrorNumber = ERROR_NUMBER(), @ErrorLine = ERROR_LINE() -- error number = 50000 :( RAISERROR (@ErrorMessage, @ErrorSeverity, @ErrorState, @ErrorNumber, @ErrorLine) END CATCH -- error number = 8134 SELECT 1/0 

如果在除了重新抛出和存储过程调用链之外的CATCH中不需要特别的错误处理,则可以使用这样简单的模板:

 create procedure someNestedSP as SET XACT_ABORT ON begin transaction -- do some work or call some other similar SP commit transaction 

如果出现任何错误,它还会回滚所有“嵌套”根事务,但代码比@ gbn的解决方案更短,更直接。 XACT_ABORT仍然照顾这里提到的大多数问题。

对于事务嵌套,可能会有额外的开销,但是可能不会太高。