【问题标题】:How to handle Transaction in Nested procedure in SQL server?如何在 SQL Server 的嵌套过程中处理事务?
【发布时间】:2019-02-18 08:46:00
【问题描述】:

我有 2 个 proc,即 Proc1 和 Proc2。 我在 proc2 中执行 proc1。在这两个过程中都有多个 DML 操作。 proc1 的输出在 proc2 中用于 DML 操作。 如果 proc2 中发生错误,则 如何在两个 proc 中处理事务以回滚所有 DML 操作?

我应该在两个 proc 中都写事务吗?

【问题讨论】:

  • SQL Server 中没有嵌套事务。在外部 proc 中启动事务,从 try 块调用内部 proc 并在 catch 上回滚。

标签: sql sql-server tsql


【解决方案1】:

我们使用基于http://www.sommarskog.se/error_handling/Part1.html 的通用错误处理程序,我们在适用时将其包含在我们的(嵌套)事务中,以确保正确管理链:

CREATE PROCEDURE [dbo].[sp_ErrorHandler](@caller VARCHAR(255))
AS BEGIN
    SET NOCOUNT ON;
    DECLARE @errmsg NVARCHAR(2048), @severity TINYINT, @state TINYINT, @errno INT, @lineno INT;
    SELECT @errmsg=REPLACE(ERROR_MESSAGE(), 'DatabaseException: ', 'DatabaseException: '+QUOTENAME(@caller)+' --> ')
         , @severity=ERROR_SEVERITY()
         , @state=ERROR_STATE()
         , @errno=ERROR_NUMBER()
         , @lineno=ERROR_LINE();
    IF @errmsg NOT LIKE 'DatabaseException%' BEGIN
        SELECT @errmsg=N'DatabaseException: '+QUOTENAME(@caller)+N', Line '+LTRIM(STR(@lineno))+N', Error '+LTRIM(STR(@errno))+N': '+@errmsg;
    END;
    RAISERROR('%s', @severity, @state, @errmsg);
END;

(在master数据库中编译并标记为系统过程)

我们使用这个错误处理程序如下。在演示中,我有一个外部 proc 和一个内部 proc 都使用事务。

CREATE PROCEDURE dbo.uspOuterProc
AS
  BEGIN
  SET NOCOUNT, XACT_ABORT ON;

  BEGIN TRY
  BEGIN TRANSACTION;

  EXEC dbo.uspInnerProc;

  PRINT 1;

  COMMIT;
  END TRY
  BEGIN CATCH
  IF @@trancount > 0
  ROLLBACK TRANSACTION;

  EXEC master.dbo.sp_ErrorHandler @caller = 'dbo.uspOuterProc';
  END CATCH;
  END;
GO

CREATE PROCEDURE dbo.uspInnerProc
AS
  BEGIN
  SET NOCOUNT, XACT_ABORT ON;

  BEGIN TRY
  BEGIN TRANSACTION;

  PRINT 2;

  SELECT 1 / 0;

  PRINT 3;

  COMMIT;
  END TRY
  BEGIN CATCH
  IF @@trancount > 0
  ROLLBACK TRANSACTION;

  EXEC master.dbo.sp_ErrorHandler @caller = 'dbo.uspInnerProc';
  END CATCH;
  END;
GO

编译并运行后:

EXEC dbo.uspOuterProc

你应该得到这个结果:

2

Msg 50000, Level 16, State 1, Procedure sp_ErrorHandler, Line 13 [Batch Start Line 48]
DatabaseException: [dbo.uspOuterProc] --> [dbo.uspInnerProc], Line 12, Error 8134: Divide by zero error encountered.

【讨论】:

    【解决方案2】:

    您可以在外部过程(在您的情况下为 proc2)中处理事务。如果 proc1 中发生任何错误,它将由 proc2 事务处理程序处理。 我假设不会直接调用proc1,它将在proc2内部调用。

    【讨论】:

      【解决方案3】:

      有 3 个基本的事务处理语句(还有一些高级的我就不提了):

      • BEGIN TRANSACTION:将 @@TRANCOUNT 会话变量提高 1。如果它从 0 变为 1,则这标志着事务的开始。任何大于 1 的值都将保持相同的交易继续进行。
      • COMMIT:将 @@TRANCOUNT 会话变量降低 1。如果它从 1 变为 0,则事务被标记为已完成,并将影响自首次创建以来所做的所有更改。李>
      • ROLLBACK:将 @@TRANCOUNT 会话变量减少到 0(无论它的值是什么),只要它至少为 1 或更高。这将关闭事务并恢复自首次创建以来所做的所有更改。

      嵌套事务是一堆BEGIN TRANSACTION 放在一起的语句。事务完全提交并且更改永久生效的唯一点是当有一个COMMIT 将事务计数从1 降低到0。这意味着您需要为每个执行的BEGIN TRANSACTION 一个COMMIT,例如金字塔。

      检查以下示例:

      BEGIN TRANSACTION
      
      SELECT @@TRANCOUNT -- 1
      
      BEGIN TRANSACTION
      
      SELECT @@TRANCOUNT -- 2
      
      COMMIT TRANSACTION
      
      SELECT @@TRANCOUNT -- 1 (no change is permanent yet, not even the last one)
      
      BEGIN TRANSACTION
      
      SELECT @@TRANCOUNT -- 2
      
      ROLLBACK
      
      SELECT @@TRANCOUNT -- 0 (all changes were discarded)
      

      当您有一个 SP 执行另一个 SP 并且两者都有自己的事务时,您唯一需要关心的是 CATCH 错误并执行正确的 ROLLBACK IF /active 事务正在进行(如果不是 ROLLBACK 语句将失败,表示没有什么可以回滚)。

      一个非常基本的CATCH 如下所示:

      BEGIN TRY
      
          BEGIN TRANSACTION
      
              /* Do some operations */
      
              /* Execute another SP that might have the following:
      
              BEGIN TRANSACTION
      
                  -- Some other operations
      
              COMMIT
      
              */
      
          COMMIT
      
      END TRY
      
      BEGIN CATCH
      
          DECLARE @v_ErrorMessage VARCHAR(MAX) = ERROR_MESSAGE()
      
          IF @@TRANCOUNT > 0 -- Only rollback if there is an active transaction
              ROLLBACK
      
          RAISERROR (@v_ErrorMessage, 16, 1)
      
      END CATCH
      

      如果您想深入了解在 SQL Server 上处理事务的最佳方式,可以阅读 this post

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-11-23
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多