【问题标题】:sql try/catch rollback/commit - preventing erroneous commit after rollbacksql try/catch rollback/commit - 防止回滚后的错误提交
【发布时间】:2014-09-28 13:52:33
【问题描述】:

我正在尝试编写一个包含事务和 try/catch 块的 MS sql 脚本。如果它捕获到异常,则回滚事务。如果不是,则提交事务。我看到一些不同的网站说要这样做:

begin transaction
begin try
    --main content of script here
end try
begin catch
    rollback transaction
end catch

commit transaction

但是即使在捕获异常的情况下,我们是否仍会点击“提交事务”行?这不会因为事务已经回滚而导致 SQL 错误吗?我认为应该这样做:

declare @success bit = 1

begin transaction
begin try
    --main content of script here
end try
begin catch
    rollback transaction
    set @success = 0
end catch

if(@success = 1)
begin
    commit transaction
end

为什么常见的解决方案不包含@success 变量?提交已回滚的事务是否不会发生 sql 错误?我是否说第一个代码示例的“提交事务”行在捕获异常的情况下仍然会被命中?

【问题讨论】:

  • 我实际上不会做这两件事。我会将提交移到 TRY 块内。不需要像您发布的其他变量。这是 TRY/CATCH 的一大优势,我们可以摆脱变量的意大利面,并在所有地方检查它们。
  • 我同意 Sean 的方法,但要确定它是否会在提交事务时导致 SQL 错误,最简单的方法是尝试一下。如果它是最被接受的方法,它可能不会。

标签: sql sql-server exception transactions try-catch


【解决方案1】:

我一直认为this was one of the better articles 在这个问题上。它包括以下示例,我认为可以清楚地说明并包括可靠的嵌套事务所需的经常被忽视的@@trancount

PRINT 'BEFORE TRY'
BEGIN TRY
    BEGIN TRAN
     PRINT 'First Statement in the TRY block'
     INSERT INTO dbo.Account(AccountId, Name , Balance) VALUES(1, 'Account1',  10000)
     UPDATE dbo.Account SET Balance = Balance + CAST('TEN THOUSAND' AS MONEY) WHERE AccountId = 1
     INSERT INTO dbo.Account(AccountId, Name , Balance) VALUES(2, 'Account2',  20000)
     PRINT 'Last Statement in the TRY block'
    COMMIT TRAN
END TRY
BEGIN CATCH
    PRINT 'In CATCH Block'
    IF(@@TRANCOUNT > 0)
        ROLLBACK TRAN;

    THROW; -- raise error to the client
END CATCH
PRINT 'After END CATCH'
SELECT * FROM dbo.Account WITH(NOLOCK)
GO

【讨论】:

  • 是否应该通过简单地将其粘贴到 SSMS 中来进行这项工作(它对我不起作用),还是我需要一个存储过程?
  • 如果您不处理嵌套事务,是否有任何理由使用@@TRANCOUNT?
  • @DavidHammond - 不,但是你通常不知道调用者是否有活动事务
  • END CATCH 之前添加; THROW 以引发错误,否则错误会被吃掉,你不会知道它发生了。
  • 先对@@trancount 点赞
【解决方案2】:

在您的第一个示例中,您是正确的。无论 try 块是否触发,批处理都会触发提交事务。

在您的第二个示例中,我同意其他评论者的观点。使用成功标志是不必要的。

我认为以下方法本质上是一种轻量级的最佳实践方法。

如果您想查看它如何处理异常,请将第二次插入的值从 255 更改为 256。

CREATE TABLE #TEMP ( ID TINYINT NOT NULL );
INSERT  INTO #TEMP( ID ) VALUES  ( 1 )

BEGIN TRY
    BEGIN TRANSACTION

    INSERT  INTO #TEMP( ID ) VALUES  ( 2 )
    INSERT  INTO #TEMP( ID ) VALUES  ( 255 )

    COMMIT TRANSACTION
END TRY
BEGIN CATCH
    DECLARE 
        @ErrorMessage NVARCHAR(4000),
        @ErrorSeverity INT,
        @ErrorState INT;
    SELECT 
        @ErrorMessage = ERROR_MESSAGE(),
        @ErrorSeverity = ERROR_SEVERITY(),
        @ErrorState = ERROR_STATE();
    RAISERROR (
        @ErrorMessage,
        @ErrorSeverity,
        @ErrorState    
        );
    ROLLBACK TRANSACTION
END CATCH

SET NOCOUNT ON

SELECT ID
FROM #TEMP

DROP TABLE #TEMP

【讨论】:

    【解决方案3】:

    我多次成功使用ms sql脚本模式,它使用Try-CatchCommit Transaction-Rollback Transaction错误跟踪

    您的 TRY 块将如下所示

     BEGIN TRY
     BEGIN TRANSACTION T
     ----
     //your script block
     ----
     COMMIT TRANSACTION T 
     END TRY
    

    您的 CATCH 块将如下所示

    BEGIN CATCH
    DECLARE @ErrMsg NVarChar(4000), 
            @ErrNum Int, 
            @ErrSeverity Int, 
            @ErrState Int, 
            @ErrLine Int, 
            @ErrProc NVarChar(200)
     SELECT @ErrNum = Error_Number(), 
           @ErrSeverity = Error_Severity(), 
           @ErrState = Error_State(), 
           @ErrLine = Error_Line(), 
           @ErrProc = IsNull(Error_Procedure(), '-')
     SET @ErrMsg = N'ErrLine: ' + rtrim(@ErrLine) + ', proc: ' + RTRIM(@ErrProc) + ', 
           Message: '+ Error_Message()
    

    您的 ROLLBACK 脚本将成为 CATCH 块的一部分,如下所示

    IF (@@TRANCOUNT) > 0 
    BEGIN
    PRINT 'ROLLBACK: ' + SUBSTRING(@ErrMsg,1,4000)
    ROLLBACK TRANSACTION T
    END
    ELSE
    BEGIN
    PRINT SUBSTRING(@ErrMsg,1,4000);   
    END
    
    END CATCH
    

    您需要将不同的脚本块用作一个块。如果 TRY 块中发生任何错误,它将进入 CATCH 块。那里设置了有关错误号、错误严重性、错误行等的各种详细信息。最后,所有这些细节都将附加到@ErrMsg 参数。然后它会检查事务计数 (@@TRANCOUNT >0) ,即事务中是否有任何东西需要回滚。如果存在则显示错误消息和ROLLBACK TRANSACTION。否则只需打印错误消息。

    我们将 COMMIT TRANSACTION T 脚本保留在 TRY 块的最后一行,以确保只有在TRY 块已成功运行。

    【讨论】:

      【解决方案4】:

      交易计数器

      --@@TRANCOUNT = 0
      begin try
      --@@TRANCOUNT = 0
      BEGIN TRANSACTION tran1
       --@@TRANCOUNT = 1
      
              --your code
              -- if failed  @@TRANCOUNT = 1
              -- if success @@TRANCOUNT = 0
      
      COMMIT TRANSACTION tran1
      
      end try
      
      begin catch
          print 'FAILED'
      end catch
      

      【讨论】:

        【解决方案5】:

        以下内容可能有用。

        来源:https://msdn.microsoft.com/en-us/library/ms175976.aspx

        BEGIN TRANSACTION;
        
        BEGIN TRY
            -- your code --
        END TRY
        BEGIN CATCH
            SELECT 
                ERROR_NUMBER() AS ErrorNumber
                ,ERROR_SEVERITY() AS ErrorSeverity
                ,ERROR_STATE() AS ErrorState
                ,ERROR_PROCEDURE() AS ErrorProcedure
                ,ERROR_LINE() AS ErrorLine
                ,ERROR_MESSAGE() AS ErrorMessage;
        
            IF @@TRANCOUNT > 0
                ROLLBACK TRANSACTION;
        END CATCH;
        
        IF @@TRANCOUNT > 0
            COMMIT TRANSACTION;
        GO
        

        【讨论】:

        • -1 ...您的 COMMIT 事务应该在 BEGIN TRY 和 END TRY 之间 - 否则,一旦出现错误,您将回滚(不抛出任何东西)然后尝试提交已关闭的事务。
        猜你喜欢
        • 2018-10-31
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-09-19
        • 1970-01-01
        • 2017-05-17
        • 2011-12-07
        • 1970-01-01
        相关资源
        最近更新 更多