XACT_ABORT 并不总是在出错时回滚事务。具体什么时候做呢?

kam*_*ilk 5 sql sql-server stored-procedures transactions

问题

的文档SET XACT_ABORT仅说明了启用此选项的效果。

当 SET XACT_ABORT 为 ON 时,如果 Transact-SQL 语句引发运行时错误,则整个事务将终止并回滚。

我不相信这就是全部真相。读完本文后,我担心如果在外部进程创建的事务中执行启用此选项的存储过程,则最终可能会回滚外部事务。幸运的是,我的担心被证明是没有根据的。然而,这意味着我现在并不真正理解如何XACT_ABORT运作。SQL Server 检查事务是否应该回滚的条件是什么?

事前调查

我进行了以下实验:(此代码的摘要如下,因为代码块之前有一个编号列表会破坏 StackOverflow 的格式,呃)

CREATE TABLE Dummy
(
    ID INT NOT NULL IDENTITY CONSTRAINT PK_Dummy PRIMARY KEY,
    Text NVARCHAR(128) NOT NULL
)

CREATE UNIQUE NONCLUSTERED INDEX IX_Dummy_Text ON dbo.Dummy(Text)

GO 

CREATE OR ALTER PROCEDURE InsertDummy
    @Text NVARCHAR(128)
AS
BEGIN
    SET NOCOUNT OFF
    SET XACT_ABORT ON

    INSERT dbo.Dummy (Text) VALUES (@Text)
END

GO

SET XACT_ABORT ON

BEGIN TRANSACTION
BEGIN TRY
    EXEC dbo.InsertDummy @Text = N'Dummy'
    EXEC dbo.InsertDummy @Text = N'Dummy' --DUPLICATE!
END TRY
BEGIN CATCH
    PRINT 'ERROR! @@TRANCOUNT is ' + CONVERT(NVARCHAR, @@TRANCOUNT)

    -- Echo the error
    DECLARE @ErrorMessage NVARCHAR(4000);  
    DECLARE @ErrorSeverity INT;  
    DECLARE @ErrorState INT;  

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

    RAISERROR (@ErrorMessage, -- Message text.  
                @ErrorSeverity, -- Severity.  
                @ErrorState -- State.  
                );  
END CATCH

PRINT 'At the end @@TRANCOUNT is ' + CONVERT(NVARCHAR, @@TRANCOUNT)
IF @@TRANCOUNT>0
    ROLLBACK
Run Code Online (Sandbox Code Playgroud)
  1. 创建具有 UNIQUE 索引的虚拟表
  2. 插入 Dummy 的存储过程。该程序使XACT_ABORT.
  3. 在一个事务中执行此过程两次的代码。第二次调用失败,因为它尝试将重复值插入到 Dummy 中。
  4. 相同的代码打印出该@@TRANCOUNT值以显示我们是否仍在交易中。它还使XACT_ABORT.

该测试的输出是:

(1 row affected)

(0 rows affected)
ERROR! @@TRANCOUNT is 1
Msg 50000, Level 14, State 1, Line 74
Cannot insert duplicate key row in object 'dbo.Dummy' with unique index 'IX_Dummy_Text'. The duplicate key value is (Dummy).
At the end @@TRANCOUNT is 1
Run Code Online (Sandbox Code Playgroud)

发生错误但事务未回滚。这个设置的工作方式显然并不像文档让我相信的那么简单。为什么交易没有回滚?

这个答案提到,XACT_ABORT仅在错误严重性至少为 16 时才回滚事务。本示例中的错误仅为 14 级。但是,即使我将INSERT过程中的 替换为RAISERROR (N'Custom error', 16, 0),事务仍然不会回滚。

更新:我发现虽然在我的测试中事务没有回滚,但它是注定的!@@TRANCOUNT是1当我执行此示例时,无论设置如何XACT_ABORT:但如果设置是ON,XACT_STATE()则-1表示不可提交事务。什么时候XACT_ABORT是OFF,XACT_STATE()是1。

New*_*Man 2

问题 “出现了错误,但事务没有回滚。这些设置的工作方式显然不像文档让我相信的那么简单。为什么事务没有回滚”

答案是 RAISERROR 不会导致 XACT_ABORT 触发!这意味着我们可能处于非常混乱的状态,事务明智 中止,中止,我们是 XACT_ABORT:ing,还是我们?!

根据 MSDN 的说法,

THROW 语句遵循 SET XACT_ABORT。RAISERROR 没有。新应用程序应使用 THROW 而不是 RAISERROR。

我们可以使用 THROW 语句来代替 RAISERROR。所以我们可以使用下面的语句来触发XACT_ABORT

TRUNCATE TABLE Dummy
GO
SET XACT_ABORT ON

BEGIN TRANSACTION
BEGIN TRY
    EXEC dbo.InsertDummy @Text = N'Dummy'
    EXEC dbo.InsertDummy @Text = N'Dummy' --DUPLICATE!
END TRY
BEGIN CATCH
THROW
END CATCH

PRINT 'At the end @@TRANCOUNT is ' + CONVERT(NVARCHAR, @@TRANCOUNT)
IF @@TRANCOUNT>0
    ROLLBACK
Run Code Online (Sandbox Code Playgroud)

输出将是;

(1 row affected)

(0 rows affected)
Msg 2601, Level 14, State 1, Procedure dbo.InsertDummy, Line 7 [Batch Start Line 5]
Cannot insert duplicate key row in object 'dbo.Dummy' with unique index 'IX_Dummy_Text'. The duplicate key value is (Dummy).
Run Code Online (Sandbox Code Playgroud)

对于更新的问题,您可以看到set xact_abort on 和 try-catch 一起