【问题标题】:XACT_ABORT doesn't always rollback the transaction on error. When does it do it exactly?XACT_ABORT 并不总是在出错时回滚事务。它什么时候做呢?
【发布时间】:2020-02-21 12:15:57
【问题描述】:

问题

SET XACT_ABORTThe documentation 仅说明启用此选项的效果。

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

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

事先调查

我进行了以下实验:(下面是这段代码的摘要,因为在代码块破坏 StackOverflow 的格式之前有一个编号列表,duh)

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
  1. 创建具有唯一索引的 Dummy 表
  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

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

This answer 提到XACT_ABORT 仅在错误严重程度至少为 16 时才回滚事务。此示例中的错误仅为 14 级。但是,即使我在过程中替换了INSERT使用RAISERROR (N'Custom error', 16, 0),事务仍然没有回滚。

更新:我发现,虽然在我的测试中事务没有回滚,但它注定了! @@TRANCOUNT1,当我执行此示例时,不管XACT_ABORT 设置如何:但如果设置为ONXACT_STATE()-1,表示一个不可提交的事务。当XACT_ABORTOFF 时,XACT_STATE()1

【问题讨论】:

  • 仅供参考,您在这些语句中嵌套了事务,这是一个神话。
  • 我推荐阅读all of this。 T-SQL 中的错误处理非常复杂,您不会从文档中获得完整的故事。
  • @Larnu 好吧,实际上我没有:这个示例中的过程只会创建一个保存点。抱歉,也许我应该通过删除此条件逻辑来简化示例,但我想坚持 SAVE TRANSACTION 文档中建议的模式以及我的实际生产代码过程使用的模式。
  • 你在你的样本中,@kamilk。您从BEGIN TRANSACTION 开始,然后继续使用EXEC dbo.InsertDummy。在dbo.InsertDummy 中,您还有一个BEGIN TRANSACTION;。因此嵌套事务。
  • @Larnu 第二个BEGIN TRANSACTION 不会被执行,因为@TranCounter 将等于1。

标签: sql sql-server stored-procedures transactions


【解决方案1】:

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

答案是 RAISERROR 不会导致 XACT_ABORT 扳机!这意味着我们可能处于非常混乱的状态交易 明智的 Abort, Abort, We Are XACT_ABORT:ing, Or Are We?!

根据 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

输出将是;

(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).

更新问题你可以看 set xact_abort on and try-catch together

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2010-11-01
    • 1970-01-01
    • 2010-09-09
    • 2014-09-23
    • 1970-01-01
    • 1970-01-01
    • 2012-06-28
    相关资源
    最近更新 更多