问题
SET XACT_ABORT的文档对启用此选项的影响只是简单介绍了一下。
当启用SET XACT_ABORT时,如果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
- 创建一个带有唯一索引的虚拟表
- 一个插入到Dummy表的存储过程。该过程启用XACT_ABORT。
- 执行此存储过程两次的代码,处于事务中。第二次调用失败,因为它试图将重复值插入到Dummy表中。
- 相同的代码打印出@@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
一个错误被引发,但事务没有回滚。这个设置的工作方式显然不像文档所说的那么简单。为什么事务没有回滚?这个答案提到,只有当错误的严重程度至少为16时,
XACT_ABORT
才会回滚事务。这个例子中的错误级别只有14。然而,即使我用RAISERROR(N'Custom error', 16, 0)
替换存储过程中的INSERT
,事务仍然没有回滚。更新:我发现,尽管在我的测试中事务没有回滚,但它已经失败了!无论XACT_ABORT
设置如何,当我执行这个示例时,@@TRANCOUNT
都是1
:但如果设置为ON
,XACT_STATE()
是-1
,表示未提交的事务。当XACT_ABORT
是OFF
时,XACT_STATE()
是1
。
BEGIN TRANSACTION
开始一个事务,然后跟着执行EXEC dbo.InsertDummy
。在dbo.InsertDummy
内部,你又有了另一个BEGIN TRANSACTION
。因此是嵌套事务。 - Thom ABEGIN TRANSACTION
不会被执行,因为@TranCounter
将等于 1。 - kamilk