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

时间:2010-01-15 18:04:33

标签: sql-server-2005 linq-to-sql stored-procedures transactions

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

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!
}

如果存储过程“MySubProcedure”在 MyStoredProcedure中调用会引发错误,我可以确定以前在MyStoredProcedure中完成的所有内容都将被回滚,所有Linq操作都由SubmitChanges将被回滚,最后会记录错误吗?或者我需要在模式中进行哪些更改以确保整个操作是原子操作,同时仍然允许单独使用子部件(即子程序仍应具有相同的原子保护)

5 个答案:

答案 0 :(得分:105)

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

这是为了处理

<强>说明:

  • 所有TXN开始和提交/回滚必须配对,以便@@TRANCOUNT在进入和退出时相同

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

    • BEGIN TRAN增加@@TRANCOUNT

    • COMMIT递减@@TRANCOUNT

    • ROLLBACK@@TRANCOUNT返回零

  • 您无法减少当前范围的@@TRANCOUNT 这就是你认为的“内部交易”

  • SET XACT_ABORT ON可以抑制因@@TRANCOUNT mis不匹配而导致的错误266 并且还在dba.se

  • 上处理此问题"SQL Server Transaction Timeout"
  • 这允许客户端TXN(如LINQ) 单个存储过程可能是分布式或XA事务的一部分,或者只是在客户端代码中启动的一个(例如.net TransactionScope)

<强>用法:

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

<强>摘要

  • 所以不要创建比你需要的更多的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有一个使用保存点的similar shell。我更喜欢原子数据库调用而不使用像文章那样的部分更新

答案 1 :(得分:10)

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

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

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

答案 2 :(得分:1)

要解决返回@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)

答案 3 :(得分:0)

- 上面的@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

答案 4 :(得分:-1)

如果除了重新抛出和存储过程调用链之外,CATCH中不需要特殊的错误处理,那么它可能适合使用这样的简单模板:

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

它还会使用所有&#34;嵌套&#34;回滚根事务。如果出现任何错误,但代码比@ gbn的解决方案更短,更直接。仍然XACT_ABORT负责处理那里提到的大多数问题。

交易嵌套可能会有额外的开销,但我想这可能不会太高。