【问题标题】:Error handling in TSQL procedureSQL 过程中的错误处理
【发布时间】:2012-03-05 10:41:40
【问题描述】:

问题总结:

我进行的错误处理似乎太复杂了,但仍然无法解决所有情况(因为可能存在事务处于不可提交状态的情况)。我怀疑我:

  • 错过了一些重要的事情并且做错了(你能解释一下吗?那我应该怎么做?)。
  • 没有错过任何事情 - 只需要接受错误处理仍然是 SQL Server 中的大问题。

您能否提供更好的解决方案(针对以下描述的情况)?

关于我的情况:

我在 SQL Server 中有(几个)存储过程,从不同的地方调用。可以概括为两种情况:

  1. 从 .NET 代码调用过程,在 SQL 过程中创建和处理事务
  2. 过程在其他过程中被调用(更具体地说是在 Service Broker 激活过程中),因此事务由外部过程处理。

我这样做了,该过程返回结果(1 表示成功,0 表示失败)+ 在出现错误时返回用于记录目的的消息。

程序内部:

  • 将 XACT_ABORT 设置为 ON; -- 交易不会因为触发器而无法提交。
  • 声明@PartOfTran 位 = 0; -- 用于保存状态:1- 如果此过程是其他事务的一部分或 0- 应该开始新事务。
  • 如果这是其他 tran 的一部分,则创建保存点。如果没有,则开始交易。
  • 开始尝试块 - 执行所有操作,如果没有错误并且如果这不是嵌套事务,则执行提交。如果它是嵌套事务,将在调用者过程中进行提交。
  • 如果出现错误:如果这是嵌套事务并且事务处于可提交状态 - 可以回滚到保存点“MyTran”。如果它不是事务的一部分,则回滚名为“MyTran”的事务。在所有其他情况下 - 只需返回错误代码和消息。

代码如下:

Create Procedure dbo.usp_MyProcedure 
(
    -- params here ...
    @ReturnCode int out, -- 1 Success, != 1 Error
    @ReturnMsg nvarchar(2048) out
)
AS
Begin
    Set NoCount ON;
    Set XACT_ABORT ON; 

    Declare @PartOfTran bit = 0;

    IF(@@TRANCOUNT > 0)
        Begin 
            SET @PartOfTran = 1;
            SAVE TRAN MyTran;
        END
    Else
        BEGIN TRAN MyTran;

    Begin Try
        -- insert table1
        -- update table2
        -- ....

        IF(@PartOfTran = 0) 
            COMMIT TRAN MyTran;
        Select @ReturnCode = 1, @ReturnMsg = Null;
    End Try
    Begin Catch
        IF (XACT_STATE() = 1 And @PartOfTran = 1) OR @PartOfTran = 0
            Rollback Tran MyTran;
        Select @ReturnCode = 0, @ReturnMsg = ERROR_MESSAGE();
    End Catch
End

其他文献:

从我最喜欢的博主那里看到:

  1. sommarskog - 但我不喜欢“outer_sp”的行“IF @@trancount > 0 ROLLBACK TRANSACTION”,因为在我的情况下,外部过程可以在事务中调用,所以在这种情况下,我有“事务计数之后EXECUTE 表示 BEGIN 和 COMMIT 语句的数量不匹配。以前的计数 = 1,当前的计数 = 0。"
  2. rusanu - 实际上和我在这里写的几乎一样(也许想法来自那篇博客文章 - 我根据我所读到的关于这个主题的所有内容编写了我的解决方案)。这篇博文仍然没有解决我应该如何处理不可提交的交易。这是 Service Broker 的问题。如果我必须回滚不可提交的事务,如何正确记录错误消息?我对此有一些想法,但所有这些似乎都是解决方法而不是优雅的解决方案。

【问题讨论】:

    标签: sql-server-2008 sql-server-2005 tsql error-handling service-broker


    【解决方案1】:

    您将无法实现仅回滚在usp_MyProcedure 中完成的工作的解决方案在任何情况下。考虑最明显的例子:死锁。当您收到异常 1205 的通知(您已被选为死锁受害者)时,事务已经回滚(以允许进度)。随着错误处理的进行,唯一安全的选择是进一步引发并重新引发,以便调用者有机会做出反应。 “不可提交的事务”只是该主题的一个变体:当调用者启动事务时,错误处理无法以调用者合理的方式从这种情况中恢复。最好的事情是加注(重新投掷)。这就是为什么我使用你在我的博客Exception HAndling and Nested Transactions中看到的模式

    在 Service Broker 上下文中考虑到这一点,这意味着没有完全防弹、异常安全的消息处理例程。如果你遇到了一个不可提交的事务(或者一个在你处理 catch 块时已经回滚的事务,比如 1205 死锁),那么你收到的整批消息都必须回滚。日志记录通常在这种情况下最外层的 catch 块之后完成(通常位于激活的过程中)。这是如何工作的伪代码:

    usp_myActivatedProc
    as
    @commited = false;
    @received = 0;
    @errors = 0;
    
    begin transaction
    begin try
      receive ... into @table;
      @received = @@row_count;
      foreach message in @table
        save transaction
        begin try
           process one message: exec usp_myProcedure @msg
        end try
        begin catch
          if xact_state()=1
            rollback to savepoint
            @errors += 1;
            -- decide what to do with failed message, log
            -- this failure may still be committed (receive won't roll back yet)
          else 
            -- this is a lost cause, re-throw
            raiserror
        end catch
        fetch next @table
      endfor
      commit
      @commited = true;
    end try
    catch
        @error_message = error_message();
        if xact_state() != 0
          rollback
    end catch
    if @commited = false
    begin
       insert into logging 'failed', @received, @error_message
    end
    

    【讨论】:

    • 万一发生死锁可以很好的解决。即使日志在 Tran 之外,它也可能失败。我的 msg 处理涉及使用 xml 以及从自定义日期格式中提取日期。我从外部系统获得的 xml。如果我做 Cast('blabla' as datetime) 我得到 Tran uncommitable,所以最终 SSBS 队列被停止。好的,我可以通过添加额外的检查来处理特殊情况——但我不能保证已经考虑了所有情况。我能做什么——我可以在日志中添加对话 guid,然后在处理消息之前检查下一次重试计数。据我了解,这是我必须这样做的方式吗? :(
    • 最终这就是为什么您要检测有害消息的原因,因为您无法预料到这种情况。在 SQL Server 2008 R2 中,您可以disable poison message detection,但这意味着您将回滚 ad-nauseam。计算回滚后可能改进处理,但不是 100% 保证的方法(同样,代码和可能出现未预料到的情况)。别忘了还有队列禁用的事件通知,您可以使用它来自动执行操作,甚至触发人工干预。
    【解决方案2】:
            -- insert table1
    IF @@ERROR > 0
            GOTO _FAIL
            -- update table2
            -- ....
    IF @@ERROR > 0
    GOTO _FAIL
    
    GOTO _SUCCESS
    
    
    _ERROR:
        ROLLBACK TRAN
        SET @ReturnCode = 1
        RETURN
    _FAIL:
        ROLLBACK TRAN
        SET @ReturnCode = 1
        RETURN
    
    _SUCCESS:
        COMMIT TRAN
    

    在 tran 末尾插入

     GOTO _SUCCESS
    

    如果没有遇到任何错误,它将提交 tran。

    AND 所有你在 tran 中的 insert 或 update 语句插入

    IF @@ERROR > 0
            GOTO _FAIL
    

    所以当它遇到错误时..它将转到

       _FAIL:
            ROLLBACK TRAN
            SET @ReturnCode = 1
            RETURN
    

    你可以在那里设置你所有的返回值

    【讨论】:

    • 好吧,它是一种老式的错误处理方式..它不能解决嵌套事务的情况..
    • 如果有保存点或开始事务 {transaction name},那么提交事务 {transaction name} 应该可以工作,是的。
    猜你喜欢
    • 2013-05-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-08-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多