我是否真的需要使用“SET XACT_ABORT ON”?

27

如果你很仔细,并且在所有操作周围使用TRY-CATCH,并在错误时回滚,你是否真的需要使用:

SET XACT_ABORT ON

换句话说,TRY-CATCH会错过哪些错误,而SET XACT_ABORT ON会处理?

6个回答

38

请记住,无论是否使用 XACT_ABORTTRY-CATCH 都无法捕获某些错误。

然而,SET XACT_ABORT ON 不会影响错误的捕获。它确保任何事务都被回滚/取消。当设置为“OFF”时,您仍然可以选择提交或回滚(取决于 xact_state)。这是 SQL 2005 中 XACT_ABORT 的主要行为变化。

它还会在客户端命令超时并且客户端发送“abort”指令时删除锁等待。如果不使用 SET XACT_ABORT,则连接保持打开状态时锁定可能会保留。我和我的同事(一位 MVP)在今年年初对此进行了彻底的测试。


1
它们会一直保留,直到连接关闭时才全部回滚。当您在客户端关闭连接时,连接池可能会使其保持活动状态的时间比您想象的更长...因此它保持打开状态,没有回滚发生。XACT_ABORT ON 强制回滚。而且在 SQL 2005 中没有不良影响。 - gbn
1
还可以参考这个相关问题 - Fernando Correia
3
“回滚保证”的规则有一个例外:如果你在使用RAISERROR时加上XACT_ABORT选项,SQL将不会自动回滚事务。在这种情况下,你必须显式地执行ROLLBACK操作。详见http://www.sommarskog.se/error-handling-I.html#XACT_ABORT。 - Dan Nolan
@gbn除了本页面底部Ian Boyd的回答外,我没有看到关于使用XACT_ABORT ON的“传统智慧”有任何变化。这些年来,对此的看法是否发生了变化,或者XACT_ABORT ON仍然是最佳实践? - markA
1
@jimdrang:请看一下我的评论,我仍在使用它。请也看一下https://dev59.com/vXI95IYBdhLWcg3w-C9b#2074139。 - gbn
显示剩余6条评论

4

我相信在执行分布式事务时,设置SET XACT_ABORT ON是必需的。

根据官方文档: 对于大多数OLE DB提供程序(包括SQL Server),在隐式或显式事务中进行数据修改语句时,必须将XACT_ABORT设置为ON。唯一不需要此选项的情况是如果提供程序支持嵌套事务。有关更多信息,请参见分布式查询和分布式事务。


2

盲目地始终使用SET XACT_ABORT ON是有一个注意事项的,最近我就中招了。

我在StackOverflow上阅读了一篇有说服力的论述,建议您应该始终使用XACT_ABORT ON。我更改了系统以在连接期间设置该选项。然而,这却导致了数据损坏和很多痛苦。

begin transaction
try
    perform insert
    catch duplicate key violation and react appropriately

    perform more actions

    commit transaction
catch
    rollback transaction
end

除了您的“更多操作”将不再在事务中发生外,其他都没有变化。因为即使您捕获了重复键违规,服务器也不再处于事务中:
begin transaction
try
    perform insert
    catch duplicate key violation and react appropriately
    transaction implicitly rolled back

    perform more actions

    commit transaction -> fails because not in a transaction
catch
    rollback transaction -> fails because not i a transaction
end

我已经改变了我的想法。永远不要使用SET XACT_ABORT ON


编辑:人们似乎认为问题来自于在没有事务的情况下尝试调用ROLLBACK TRANSACTION。他们认为如果没有正在进行的事务,则可以通过不调用ROLLBACK来解决问题。

让我们使用一些伪代码,更改名称以保护NDA:

const
   SQLNativeErrorPrimaryKeyViolation = 2627; //Primary keys. 2601 is for other unique index

void x(String sql)
{
   database.Connection.ExecuteNoRecords(sql);
}

这是一种使答案更易读的学究式方法;我们使用x来表示执行某个SQL语句:

void DoStuff()
{
   x("BEGIN TRANSACTION");
   try
   {
      try
      {
         x("INSERT INTO Patrons (AccountNumber, Name, Gender)"+
           "VALUES (619, 'Shelby Jackson', 'W'"); 
      } 
      catch (ESqlServerException e)
      {
         //check if the patron already exists (or some other hypothetical situation arises)
         if (e.NativeError == SQLNativeErrorPrimaryKeyViolation)
         {
            //This patron already exists. Set their frob to grob because contoso the blingblong
            x("UPDATE Patrons SET Frob='Grob' WHERE AccountNumber = 619");

            //20110918: Dont forget we also need to bang the gardinker
            x("EXECUTE BangTheGardinker @floof=619");
         }
         else
            throw e;
      }

      //Continue with the stuff
      x("EXECUTE Frob('{498BBB4D-D9F7-4438-B7A6-4AB5D57937C0}')");

      //All done, commit the transaction
      x("COMMIT TRANSACTION");       
   }
   catch (Exception e)
   {
      //Something bad happened, rollback the transaction 
      //(if SQL Server didn't kill the transaction without our permission)
      x("IF @@TRANCOUNT > 0 ROLLBACK TRANSACTION");


      throw e;
   }
}

XACT_ABORT ON很酷,让我们使用它

所以,那段代码有效。如果出现我们预期的错误,我们会处理它并继续执行。这被称为处理错误。如果发生一些未知的异常(我们没有预料到的情况),我们会回滚任何可能正在进行的事务。

现在让我们看看是否盲目地遵循XACT_ABORT应该始终开启的建议:

 DbConnection Connection()
 {
    if (_connection == null)
    {
       _connection = new SqlConnection();

       //It is generally recommended that you always have xact_abort on.
       //If a connection is closed with a transaction still in progress
       //it still leaves locks held until that connection is finally recycled
       //Also, when querying linked severs in a client-side transaction, the
       //operation won't work until xact_abort is on (SQL Server will throw an saying xactabort is off
       _connection.ExecuteNoRecords("SET XACT_ABORT ON");
    }

    return _connection;
 }

void x(String sql)
{
   database.Connection.ExecuteNoRecords(sql);
}

您看到了会在DoStuff中引起的数据损坏吗?

DoStuff正确地编写以处理错误情况。但是,将XACT_ABORT ON引入连接将导致数据库损坏。对于那些没有发现漏洞的人,请跟随代码进行分析:

void DoStuff()
{
   x("BEGIN TRANSACTION");
   try
   {
      try
      {
         x("INSERT INTO Patrons (AccountNumber, Name, Gender)"+
           "VALUES (619, 'Shelby Jackson', 'W'"); 

      } 
      catch (ESqlServerException e)
      {
         //WARNING: WE ARE NO LONGER OPERATING IN A TRANASCTION
         //Because XACT_ABORT is on, the transaction that we started has been implicitly rolled back.
         //From now on, we are no longer in a transaction. If another error happens
         //the changes we make cannot be rolled back

         //check if the patron already exists (or some other hypothetical situation arises)
         if (e.NativeError == SQLNativeErrorPrimaryKeyViolation)
         {
            //WARNING: This update happens outside of any transaction!
            //This patron already exist. Set their frob to grob because contoso the blingblong
            x("UPDATE Patrons SET Frob='Grob' WHERE AccountNumber = 619");

            //WARNING: This stored procedure happens outside of any transaction!
            //20110918: Dont forget we also need to bang the gardinker
            x("EXECUTE BangTheGardinker @floof=619");
         }
         else
            throw e;
      }

      //WARNING: This stored procedure happens outside of any transaction!
      //If any error happens from
      //Continue with the stuff
      x("EXECUTE Frob('{498BBB4D-D9F7-4438-B7A6-4AB5D57937C0}')");

      //WARNING: This stored procedure happens outside of any transaction. It will throw:
      //   Msg 3902, Level 16, State 1, Line 1
      //   The COMMIT TRANSACTION request has no corresponding BEGIN TRANSACTION.
      //All done, commit the transaction
      x("COMMIT TRANSACTION");       
   }
   catch (Exception e)
   {
      //If there was an error during Frob, we would want to catch it and roll everything back.
      //But since SQL Server ended the transaction, we have no way to rollback the changes

      //And even if the call to Frob (or Updating the patron's Grob, or Banging the Gardinder)
      //didn't fail, the call to COMMIT TRANSACTION will throw an error

      //Either way, we have detected an error condition that cannot be rolled back in the database


      //Something bad happened, rollback the transaction 
      //(if SQL Server didn't kill the transaction without our permission)
      x("IF @@TRANCOUNT > 0 ROLLBACK TRANSACTION");


      throw e;
   }
}

原本正确且正常运行的代码,一旦我开启了 XACT_ABORT ON,就会出现错误、甚至导致数据库损坏。


2
首先,如果出现触发回滚或错误的情况,交易可能不存在。为什么不使用XACT_STATE()?https://dev59.com/vXI95IYBdhLWcg3w-C9b#2074139。其次,在重复错误后,我看不到在错误条件下“执行更多操作”的理由。请给我一个更好的例子:而且我从来没有遇到过模式损坏的情况... - gbn
1
我不确定人们是否会收到他们评论过的答案编辑的通知。@gbn - 这些信息似乎非常相关和有用。您是否建议我们为了其他人的利益/输入而开始一个新问题? - markA
3
你的伪代码看起来像某种应用程序编程语言,而我认为“覆盖”xact_abort的try/catch是T-SQL的一种。也许你的情况发生在从调用者到SQL Server,而不是在数据库引擎中运行的T-SQL? - user800576
1
@Paul 你说得完全正确,这是源代码的问题。关键在于,将 SET XACT_ABORT ON 添加到现有代码中可能会使其从正常工作的代码变成数据损坏。 - Ian Boyd
@variable 将调用.BeginTransaction移到try块中不会产生任何积极影响。事实上,这样做只会导致更多问题:如果将.BeginTransaction移到try块内,而在调用.BeginTransaction时出现错误,那么异常处理程序将故意尝试回滚一个从未启动的事务。编写try-catch的正确方式是捕获需要处理错误的代码。 - undefined
显示剩余10条评论

2

我的理解是,即使使用了try catch,并且在catch块中没有使用rollback语句,当XACT_ABORT为ON时,任何未提交的事务都将被回滚。


1

XACT_ABORT确实会影响错误处理:当遇到错误时,它将中止整个批处理,并且产生错误的行之后的任何代码(包括错误检查!)都将永远不会执行。但有两个例外情况:XACT_ABORT被TRY...CATCH覆盖(CATCH块将始终执行,并且事务不会自动回滚,只会变为不可提交状态),并且XACT_ABORT将忽略RAISERROR。


1
不完全正确:并非所有的事务都会失败,有些仍然可以提交。此外,CATCH块无法捕获所有错误。 - A-K

0

当触发器中的XACT_ABORT设置为OFF,并且我在触发器主体中调用RAISEERROR时,更改不会回滚。


注意:RAISERROR(注意一个E)不会触发回滚,而应该使用Throw。参考此链接顶部的“注意”内容:https://msdn.microsoft.com/en-us/library/ms188792.aspx - JohnLBevan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接