SQL Server 2005:使用XACT_ABORT = ON与TRY ... CATCH混淆

时间:2015-01-23 15:07:42

标签: sql sql-server sql-server-2005 try-catch xact-abort

我对使用XACT_ABORT ON和TRY ... CATCH构造稍微感到困惑,以便在TRY块中出现错误时尝试回滚CATCH块中的事务。

我有一个像这样结构化的存储过程(当然简化):

CREATE PROCEDURE dbo.usp_clean_and_re_Insert
AS
   SET XACT_ABORT ON;

   BEGIN TRY

      BEGIN TRANSACTION

      -- first clear the table
      DELETE FROM dbo.table1

      -- re-populate the table
      INSERT INTO dbo.table1
      (col1, col2, col3)
      SELECT  1
              ,dbo.fn_DoSomething('20150101')
              ,dbo.fn_DoSomething('20150123')

      COMMIT TRANSACTION

   END TRY

BEGIN CATCH
-- Test XACT_STATE for 0, 1, or -1.
-- If 1, the transaction is committable.
-- If -1, the transaction is uncommittable and should 
--     be rolled back.
-- XACT_STATE = 0 means there is no transaction and
--     a commit or rollback operation would generate an error.

-- Test whether the transaction is uncommittable.
IF (XACT_STATE()) = -1
BEGIN
    PRINT 'The transaction is in an uncommittable state.' +
          ' Rolling back transaction.'
    ROLLBACK TRANSACTION;
END;

-- Test whether the transaction is active and valid.
IF (XACT_STATE()) = 1
BEGIN
    PRINT 'The transaction is committable.' + 
          ' Committing transaction.'
    COMMIT TRANSACTION;   
END;
END CATCH;

所以SP的工作方式如下:如果事务在任何时候都失败,它应该回滚。因此,当插入位失败时,应该回滚删除位,即表应该与之前的状态相同。

现在,让我们说在运行时dbo.fn_DoSomething()函数不可用(DBA错误地删除了它)。上面写的SP按预期工作,即事务被回滚,表保持不变,SSMS中显示的错误消息如下所示:

" 消息208,级别16,状态1,过程usp_clean_and_re_Insert,第15行 无效的对象名称' dbo.fn_DoSOmething'。"

但由于某种原因,来自CATCH块的PRINT语句似乎没有执行,即我在SSMS中看不到它们?关于TRY ... CATCH的Microsoft文档说如果在TRY块中执行期间发生错误,则执行被传递到CATCH块(https://msdn.microsoft.com/en-us/library/ms175976(v=sql.90).aspx)。

但是,如果我删除XACT_ABORT ON,事情就会变得更加奇怪:

  1. PRINT语句仍未出现在SSMS中

  2. 正确显示与上述相同的错误,即

  3. " 消息208,级别16,状态1,过程usp_clean_and_re_Insert,第15行 无效的对象名称' dbo.fn_DoSOmething'。"

    1. 最后一个错误是:
    2. "消息266,级别16,状态2,过程usp_clean_and_re_Insert,第52行 EXECUTE之后的事务计数表示缺少COMMIT或ROLLBACK TRANSACTION语句。先前的计数= 0,当前计数= 1."

      这导致表被锁定,直到我断开SSMS(运行SP的查询窗口),之后表再次可用,所有结果都保持不变(因此数据库引擎必须隐式回滚不可提交的事务)。 / p>

      阅读有关此错误消息的其他帖子(例如:Transaction count after EXECUTE indicates a mismatching number of BEGIN and COMMIT statements. Previous count = 1, current count = 0),我知道我需要检查CATCH块中的XACT_STATE并回滚不可提交的事务(这是来自:{{ 3}}),但这正是我在上面的SP中所做的,但是在我断开SSMS之前事务没有被回滚(没有XACT_ABORT ON)?

      我很困惑!总结:

      1. 为什么我在SSMS中看不到PRINT语句?

      2. 为什么在从存储过程中删除XACT_ABORT ON时,不会执行CATCH块中的ROLLBACK TRANSACTION?

      3. 如果XACT_ABORT ON似乎独自完成这项任务,为什么要使用TRY ... CACTH?即如果我删除Try..catch并将XACT_ABORT保留为ON它会使事务回滚,那么为什么我需要在catch块中使用隐式ROLLBACK TRANSACTION进行TRY CATCH?

1 个答案:

答案 0 :(得分:0)

我认为,我可能错了,XACT_ABORT在这种情况下不起作用,因为你还没有真正开始交易。您的功能不存在,这意味着在您触摸数据库之前,SQL Server会使您的事务失败。阅读XACT_STATE的手册页,以及提供的示例,看起来您必须实际上在读/写上失败。你的查询没有那么做,因为优化器看到你有一个语法错误(调用不存在的函数,你应该在它应该被执行时创建它)。

如果你阅读了这个页面(XACT_STATE的手册页)和可用的例子,它强烈建议只在遇到约束错误时才设置XACT_STATE,只有在实际尝试更改数据时才会发生:

https://msdn.microsoft.com/en-us/library/ms189797.aspx

作为一种测试方法,您可以让分析器监视dbo.table1上的事务。我敢打赌,在查询中使用XACT_ABORT ON,删除不会发生。删除它后,优化器可能会选择一个允许删除运行的不同执行计划。