包含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
仍然照顾这里提到的大多数问题。
对于事务嵌套,可能会有额外的开销,但是可能不会太高。