0

当 TRY 块中出现错误时,我对使用 XACT_ABORT ON 和 TRY...CATCH 构造来尝试回滚 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. 与上面相同的错误正确显示,即

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

  1. 有一个最后的错误说:

“消息 266,级别 16,状态 2,过程 usp_clean_and_re_Insert,第 52 行 EXECUTE 后的事务计数表明缺少 COMMIT 或 ROLLBACK TRANSACTION 语句。先前计数 = 0,当前计数 = 1。”

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

阅读有关此错误消息的其他帖子(例如这个:EXECUTE 后的事务计数表示 BEGIN 和 COMMIT 语句的数量不匹配。以前的计数 = 1,当前计数 = 0),我知道我需要检查 CATCH 块中的 XACT_STATE并回滚不可提交的事务(这与来自:https ://msdn.microsoft.com/en-us/library/ms189797.aspx 的建议相同),但这正是我在上述 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 ,它会回滚事务,那么为什么我需要在 catch 块中使用隐式 ROLLBACK TRANSACTION 的 TRY CATCH 呢?

4

1 回答 1

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 时,删除不会发生。删除后,优化器可能会选择不同的执行计划来允许删除运行。

于 2015-01-23T16:18:31.060 回答