【问题标题】:SQL Server - transactions roll back on error?SQL Server - 事务回滚错误?
【发布时间】:2010-12-17 12:12:55
【问题描述】:

我们有在 SQL Server 2005 上运行某些 SQL 的客户端应用程序,例如:

BEGIN TRAN;
INSERT INTO myTable (myColumns ...) VALUES (myValues ...);
INSERT INTO myTable (myColumns ...) VALUES (myValues ...);
INSERT INTO myTable (myColumns ...) VALUES (myValues ...);
COMMIT TRAN;

由一个长字符串命令发送。

如果其中一个插入失败,或者命令的任何部分失败,SQL Server 是否回滚事务?如果它不回滚,我是否必须发送第二个命令来回滚它?

我可以详细说明我正在使用的 api 和语言,但我认为 SQL Server 应该对任何语言做出相同的响应。

【问题讨论】:

标签: sql sql-server sql-server-2005 transactions


【解决方案1】:

您是正确的,整个事务将被回滚。您应该发出命令将其回滚。

您可以将其包装在 TRY CATCH 块中,如下所示

BEGIN TRY
    BEGIN TRANSACTION

        INSERT INTO myTable (myColumns ...) VALUES (myValues ...);
        INSERT INTO myTable (myColumns ...) VALUES (myValues ...);
        INSERT INTO myTable (myColumns ...) VALUES (myValues ...);

    COMMIT TRAN -- Transaction Success!
END TRY
BEGIN CATCH
    IF @@TRANCOUNT > 0
        ROLLBACK TRAN --RollBack in case of Error

    -- <EDIT>: From SQL2008 on, you must raise error messages as follows:
    DECLARE @ErrorMessage NVARCHAR(4000);  
    DECLARE @ErrorSeverity INT;  
    DECLARE @ErrorState INT;  

    SELECT   
       @ErrorMessage = ERROR_MESSAGE(),  
       @ErrorSeverity = ERROR_SEVERITY(),  
       @ErrorState = ERROR_STATE();  

    RAISERROR (@ErrorMessage, @ErrorSeverity, @ErrorState);  
    -- </EDIT>
END CATCH

【讨论】:

  • 我更喜欢 DyingCactus 的解决方案,他只需要更改 1 行代码。如果出于某种原因更好(或更可靠),请告诉我。
  • try catch 使您能够捕获(并可能修复)错误并在需要时引发自定义错误消息。
  • “捕获和记录”比“捕获和修复”更频繁,我想。
  • 至少在 SQL Server 2008R2 及更高版本中,RAISERROR 的语法不正确。请参阅msdn.microsoft.com/en-us/library/ms178592.aspx 了解正确的语法。
  • @BornToCode 确保事务存在。假设您已在给定条件下回滚事务(在try 中),但之后代码失败。没有更多交易,但您仍将进入catch
【解决方案2】:

这里是使用 MSSQL Server 2016 获取错误消息的代码:

BEGIN TRY
    BEGIN TRANSACTION 
        -- Do your stuff that might fail here
    COMMIT
END TRY
BEGIN CATCH
    IF @@TRANCOUNT > 0
        ROLLBACK TRAN

        DECLARE @ErrorMessage NVARCHAR(4000) = ERROR_MESSAGE()
        DECLARE @ErrorSeverity INT = ERROR_SEVERITY()
        DECLARE @ErrorState INT = ERROR_STATE()

    -- Use RAISERROR inside the CATCH block to return error  
    -- information about the original error that caused  
    -- execution to jump to the CATCH block.  
    RAISERROR (@ErrorMessage, @ErrorSeverity, @ErrorState);
END CATCH

【讨论】:

  • 我不得不使用DECLARE @Var TYPE; SET @Var = ERROR; 在 sql server 2005 中引发错误。否则上述引发错误的代码也适用于旧数据库。试图为局部变量分配默认值是导致问题的原因。
  • 可以使用简单的THROW;而不是 RAISERROR 和 ERROR_* 声明。
  • 虽然我们可以使用“set xact_abort on”,但它允许对事件进行更多控制,并且更具可读性。
【解决方案3】:

来自 MDSN 文章,Controlling Transactions (Database Engine)

如果批处理中发生运行时语句错误(例如违反约束),数据库引擎中的默认行为是仅回滚产生​​错误的语句。您可以使用 SET XACT_ABORT 语句更改此行为。执行 SET XACT_ABORT ON 后,任何运行时语句错误都会导致当前事务的自动回滚。编译错误(例如语法错误)不受 SET XACT_ABORT 影响。有关详细信息,请参阅 SET XACT_ABORT (Transact-SQL)。

在您的情况下,当任何插入失败时,它将回滚整个事务。

【讨论】:

  • 我们需要什么来处理语法错误?或编译错误?如果他们中的任何一个发生整个事务应该回滚
  • 捕获编译/语法错误是 SSDT 项目的用途。 :-)
【解决方案4】:

您可以在事务前加上set xact_abort on,以确保sql在出错时自动回滚。

【讨论】:

  • 这是否适用于 MS SQL 2K 及更高版本?这似乎是最简单的解决方案。
  • 它出现在 2000、2005 和 2008 年的文档中,所以我认为是的。我们在 2008 年使用它。
  • 我需要关闭它还是每次会话都关闭它?
  • @Marc xact_abort 的范围是在连接级别。
  • @AlexMcMillan DROP PROCEDURE 语句修改数据库结构,与 INSERT 不同,它只处理数据。所以它不能被包装在一个事务中。我过于简单化了,但基本上就是这样。
【解决方案5】:

如果其中一个插入失败,或者命令的任何部分失败,SQL Server 是否回滚事务?

不,它没有。

如果不回滚,是否必须发送第二个命令才能回滚?

当然,您应该发出 ROLLBACK 而不是 COMMIT

如果你想决定是提交还是回滚事务,你应该从语句中删除COMMIT语句,检查插入的结果,然后根据结果发出COMMITROLLBACK支票。

【讨论】:

  • 所以如果我得到一个错误,说“主键冲突”我需要发送第二个调用来回滚吗?我想这是有道理的。如果在长时间运行的 SQL 语句期间出现连接断开等网络相关错误会怎样?
  • 当连接超时时,底层网络协议(例如Named PipesTCP)会中断连接。当连接断开时,SQL Server 会停止所有当前正在运行的命令并回滚事务。
  • 所以 DyingCactus 的解决方案看起来解决了我的问题,感谢您的帮助。
  • 如果您需要在 any 错误时中止,那么是的,这是最好的选择。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多