我一直在阅读 MSDNTRY...CATCH
和XACT_STATE
.
它具有以下示例,用于XACT_STATE
在构造CATCH
块中TRY…CATCH
确定是提交还是回滚事务:
USE AdventureWorks2012;
GO
-- SET XACT_ABORT ON will render the transaction uncommittable
-- when the constraint violation occurs.
SET XACT_ABORT ON;
BEGIN TRY
BEGIN TRANSACTION;
-- A FOREIGN KEY constraint exists on this table. This
-- statement will generate a constraint violation error.
DELETE FROM Production.Product
WHERE ProductID = 980;
-- If the delete operation succeeds, commit the transaction. The CATCH
-- block will not execute.
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;
GO
我不明白的是,我为什么要关心并检查XACT_STATE
返回的内容?
请注意,该标志在示例XACT_ABORT
中设置为ON
。
如果块内有足够严重的错误TRY
,控制将传递到CATCH
. 所以,如果我在里面CATCH
,我知道事务有问题,在这种情况下唯一明智的做法就是回滚它,不是吗?
但是,来自 MSDN 的这个例子暗示了在某些情况下,控制权被传递到CATCH
,但提交事务仍然是有意义的。有人可以提供一些实际的例子,什么时候发生,什么时候有意义?
我看不到在什么情况下可以通过CATCH
设置为.XACT_ABORT
ON
MSDN文章SET XACT_ABORT
有一个示例,当事务中的某些语句成功执行而某些语句XACT_ABORT
设置为时失败OFF
,我理解。但是,SET XACT_ABORT ON
如何在块XACT_STATE()
内返回 1呢?CATCH
最初,我会这样编写这段代码:
USE AdventureWorks2012;
GO
-- SET XACT_ABORT ON will render the transaction uncommittable
-- when the constraint violation occurs.
SET XACT_ABORT ON;
BEGIN TRY
BEGIN TRANSACTION;
-- A FOREIGN KEY constraint exists on this table. This
-- statement will generate a constraint violation error.
DELETE FROM Production.Product
WHERE ProductID = 980;
-- If the delete operation succeeds, commit the transaction. The CATCH
-- block will not execute.
COMMIT TRANSACTION;
END TRY
BEGIN CATCH
-- Some severe problem with the transaction
PRINT 'Rolling back transaction.';
ROLLBACK TRANSACTION;
END CATCH;
GO
考虑到 Max Vernon 的回答,我会这样编写代码。他表明,在尝试之前检查是否存在活动事务是有意义的ROLLBACK
。尽管如此,区块可以有注定SET XACT_ABORT ON
的交易或根本没有交易。CATCH
所以,无论如何,没有什么可做的COMMIT
。我错了吗?
USE AdventureWorks2012;
GO
-- SET XACT_ABORT ON will render the transaction uncommittable
-- when the constraint violation occurs.
SET XACT_ABORT ON;
BEGIN TRY
BEGIN TRANSACTION;
-- A FOREIGN KEY constraint exists on this table. This
-- statement will generate a constraint violation error.
DELETE FROM Production.Product
WHERE ProductID = 980;
-- If the delete operation succeeds, commit the transaction. The CATCH
-- block will not execute.
COMMIT TRANSACTION;
END TRY
BEGIN CATCH
-- Some severe problem with the transaction
IF (XACT_STATE()) <> 0
BEGIN
-- There is still an active transaction that should be rolled back
PRINT 'Rolling back transaction.';
ROLLBACK TRANSACTION;
END;
END CATCH;
GO
It turns out that transaction can not be committed from inside the
CATCH
block ifXACT_ABORT
is set toON
.The example from MSDN is somewhat misleading, because the check implies that
XACT_STATE
can return 1 in some cases and it may be possible toCOMMIT
the transaction.It is not true,
XACT_STATE
will never return 1 insideCATCH
block ifXACT_ABORT
is set toON
.It seems that the MSDN sample code was meant to primarily illustrate the use of
XACT_STATE()
function regardless of theXACT_ABORT
setting. The sample code looks generic enough to work with bothXACT_ABORT
set toON
andOFF
. It is just that withXACT_ABORT = ON
the checkIF (XACT_STATE()) = 1
becomes unnecessary.There is a very good detailed set of articles about Error and Transaction Handling in SQL Server by Erland Sommarskog. In Part 2 - Classification of Errors he presents a comprehensive table that puts together all classes of errors and how they are handled by SQL Server and how
TRY ... CATCH
andXACT_ABORT
changes the behaviour.The last column in the table answers the question. With
TRY-CATCH
and withXACT_ABORT ON
the transaction is doomed in all possible cases.One note outside the scope of the question. As Erland says, this consistency is one of the reasons to set
XACT_ABORT
toON
:TL;DR / 执行摘要:关于问题的这一部分:
我现在已经对此进行了很多测试,但我找不到任何在何时
XACT_STATE()
返回块1
内且会话属性为is的情况。事实上,根据SET XACT_ABORT的当前 MSDN 页面:CATCH
@@TRANCOUNT > 0
XACT_ABORT
ON
该声明似乎与您的推测和我的发现一致。
是的,但该示例中的语句不在块内
TRY
。块中的那些相同语句TRY
仍将阻止执行导致错误的语句之后的任何语句,但假设XACT_ABORT
是OFF
,当控制权传递给CATCH
块时,事务仍然在物理上有效,因为所有先前的更改确实发生而没有错误并且可以承诺,如果这是愿望的话,或者他们可以回滚。另一方面,如果XACT_ABORT
是,ON
那么任何先前的更改都会自动回滚,然后您可以选择: a) 发出ROLLBACK
这主要是对这种情况的接受,因为事务已经回滚减去重置@@TRANCOUNT
为0
,或者 b) 得到一个错误。没有太多选择,是吗?这个难题的一个可能重要的细节在该文档中并不明显,
SET XACT_ABORT
即该会话属性,甚至该示例代码,自SQL Server 2000以来就已经存在(版本之间的文档几乎相同),早于TRY...CATCH
构造在 SQL Server 2005 中引入。再次查看该文档并查看示例(没有)TRY...CATCH
,使用XACT_ABORT ON
导致事务立即回滚:没有“不可提交”的事务状态(请注意,在该文档中的所有“不可提交”事务状态SET XACT_ABORT
)。我认为有理由得出以下结论:
TRY...CATCH
产生了对新事务状态(即“不可提交”)和XACT_STATE()
获取该信息的函数的需求。XACT_STATE()
入一个块才有意义:CATCH
XACT_ABORT
是OFF
(否则XACT_STATE()
应该总是返回-1
,@@TRANCOUNT
这就是你所需要的)CATCH
如果调用是嵌套的,则在块中或链上的某个地方有逻辑,这会进行更改(COMMIT
甚至是任何 DML、DDL 等语句),而不是执行ROLLBACK
. (这是一个非常非典型的用例)** 请参阅底部的注释,在 UPDATE 3 部分,关于 Microsoft 的非官方建议始终检查XACT_STATE()
而不是@@TRANCOUNT
,以及为什么测试表明他们的推理没有成功。TRY...CATCH
构造在很大程度上已经淘汰了XACT_ABORT ON
session 属性,因为它提供了对 Transaction 更大程度的控制(您至少可以选择COMMIT
,前提是XACT_STATE()
不返回-1
)。另一种看待这个问题的方式是,在 SQL Server 2005 之前,与在每个语句之后进行检查相比,
XACT_ABORT ON
提供了一种在发生错误时停止处理的简单可靠的方法。@@ERROR
XACT_STATE()
是错误的,或者充其量是误导性的,因为它显示了检查XACT_STATE() = 1
whenXACT_ABORT
isON
。长部分;-)
是的,MSDN 上的示例代码有点令人困惑(另请参阅:@@TRANCOUNT (Rollback) vs. XACT_STATE);-)。而且,我觉得它具有误导性,因为它要么显示一些没有意义的东西(因为你要问的原因:你甚至可以在
CATCH
块中拥有一个“可提交”的交易XACT_ABORT
isON
),或者即使它是可能的,它仍然专注于很少有人想要或需要的技术可能性,而忽略了人们更可能需要它的原因。我认为,如果我们确保我们在某些词语和概念的含义上意见一致,将会有所帮助:
“足够严重的错误”:为了清楚起见,TRY...CATCH将捕获大多数错误。不会被捕获的列表在链接的 MSDN 页面上列出,在“不受 TRY…CATCH 构造影响的错误”部分下。
“如果我在 CATCH 中,我知道事务有问题”(添加了 em phas):如果“事务”是指您通过将语句分组到显式事务中确定的逻辑工作单元,那么很可能是的。我认为我们大多数 DB 人员会倾向于同意回滚是“唯一明智的做法”,因为我们可能对我们如何以及为什么使用显式事务有类似的看法,并设想哪些步骤应该构成一个原子单元工作的。
但是,如果您指的是被分组到显式事务中的实际工作单元,那么不,您不知道事务本身存在问题。您只知道在显式定义的事务中执行的语句引发了错误。但它可能不是 DML 或 DDL 语句。即使它是一个 DML 语句,事务本身也可能仍然是可提交的。
鉴于上述两点,我们可能应该区分“不能”提交的事务和“不想”提交的事务。
当
XACT_STATE()
返回 a1
时,这意味着 Transaction 是“可提交的”,您可以在COMMIT
or之间进行选择ROLLBACK
。您可能不想提交它,但如果出于某些难以想出的示例的原因您想要提交,至少您可以,因为事务的某些部分确实成功完成。但是当
XACT_STATE()
返回 a时-1
,您确实需要这样做,ROLLBACK
因为 Transaction 的某些部分进入了错误状态。现在,我同意如果控制权已传递给 CATCH 块,那么只需检查就足够有意义了@@TRANCOUNT
,因为即使您可以提交事务,您为什么要这样做?但是,如果您在示例的顶部注意到,设置的设置会
XACT_ABORT ON
有所改变。您可能会遇到常规错误,这样做之后将在is和 XACT_STATE() 返回BEGIN TRAN
时将控制权传递给 CATCH 块。但是,如果 XACT_ABORT 是,那么对于任何 'ol 错误,事务都会“中止”(即无效),然后将返回。在这种情况下,在块内检查似乎没用,因为它似乎总是返回when is 。XACT_ABORT
OFF
1
ON
XACT_STATE()
-1
XACT_STATE()
CATCH
-1
XACT_ABORT
ON
那么是
XACT_STATE()
为了什么呢?一些线索是:的 MSDN 页面
TRY...CATCH
,在“Uncommittable Transactions and XACT_STATE”部分下,说:SET XACT_ABORT的 MSDN 页面,在“备注”部分下,说:
和:
BEGIN TRANSACTION的 MSDN 页面在“备注”部分下说:
最适用的用法似乎是在链接服务器 DML 语句的上下文中。我相信几年前我自己也遇到过这种情况。我不记得所有细节,但它与远程服务器不可用有关,并且由于某种原因,该错误没有在 TRY 块中被捕获并且从未被发送到 CATCH ,所以它确实不应该有的 COMMIT。当然,这可能是没有
XACT_ABORT
设置到ON
而不是没有检查的问题XACT_STATE()
,或者可能两者兼而有之。我确实记得读过一些内容,如果您使用链接服务器和/或分布式事务,那么您需要使用XACT_ABORT ON
和/或XACT_STATE()
,但我现在似乎找不到该文档。如果我找到它,我会用链接更新它。尽管如此,我已经尝试了几件事,但无法找到一个场景,它可以通过报告
XACT_ABORT ON
将控制权传递给该CATCH
块。XACT_STATE()
1
试试这些例子,看看
XACT_ABORT
对 的值的影响XACT_STATE()
:更新
虽然不是原始问题的一部分,但基于对此答案的这些评论:
在
XACT_ABORT ON
到处使用之前,我会问:这里到底得到了什么?我没有发现有必要这样做并且通常主张您应该仅在必要时使用它。无论您是否想ROLLBACK
通过使用@Remus 的答案中显示的模板,或者我多年来一直使用的模板,基本上是相同的,但没有保存点,都可以轻松处理,如本答案所示(处理嵌套调用):我们是否需要在 C# 代码和存储过程中处理事务
更新 2
我做了更多的测试,这次是通过创建一个小的 .NET 控制台应用程序,在应用程序层中创建一个事务,在执行任何
SqlCommand
对象(即 viausing (SqlTransaction _Tran = _Connection.BeginTransaction()) { ...
)之前,以及使用批处理中止错误而不是仅仅一个语句-aborting 错误,发现:@@TRANCOUNT
仍然 > 0。COMMIT
,因为这会生成一个错误,指出该事务是“不可提交的”。您也不能忽略它/什么也不做,因为当批处理完成时会生成一个错误,说明该批处理以一个挥之不去的、不可提交的事务完成并且它将被回滚(所以,嗯,如果它无论如何都会自动回滚,为什么要抛出错误?)。因此,您必须发出一个明确的ROLLBACK
,也许不是在立即CATCH
块中,而是在批处理结束之前。TRY...CATCH
构造中,whenXACT_ABORT
isOFF
,如果发生在TRY
块之外会自动终止事务的错误,例如批处理中止错误,将撤消工作但不会终止事务,将其保持为“不可提交”。发出 aROLLBACK
更像是结束交易所需的形式,但工作已经回滚。XACT_ABORT
is时ON
,大多数错误都作为批处理中止,因此其行为与上面的项目符号点(#3)中描述的一样。XACT_STATE()
,至少在一个CATCH
块中,如果在错误发生时存在活动事务,则将显示-1
批处理中止错误。XACT_STATE()
1
即使没有活动的事务,有时也会返回。如果@@SPID
(除其他外)与SELECT
一起在列表中XACT_STATE()
,则XACT_STATE()
在没有活动事务时将返回 1。此行为始于 SQL Server 2012,并存在于 2014 年,但我尚未在 2016 年测试过。考虑到以上几点:
XACT_STATE()
鉴于第 4 点和第 5 点,由于大多数(或所有?)错误将导致事务“不可提交”,因此在何时检查CATCH
块似乎完全没有意义,因为返回的值将始终为.XACT_ABORT
ON
-1
XACT_STATE()
in theCATCH
block whenXACT_ABORT
isOFF
makes more sense because the return value will at least have some variation since it will return1
for statement-aborting errors. However, if you code like most of us, then this distinction is meaningless since you will be callingROLLBACK
anyway simply for the fact that an error occurred.COMMIT
in theCATCH
block, then check the value ofXACT_STATE()
, and be sure toSET XACT_ABORT OFF;
.XACT_ABORT ON
seems to offer little to no benefit over theTRY...CATCH
construct.XACT_STATE()
provides a meaningful benefit over simply checking@@TRANCOUNT
.XACT_STATE()
returns1
in aCATCH
block whenXACT_ABORT
isON
. I think it is a documentation error.XACT_ABORT ON
, it's a moot point since an error happening in aTRY
block will automatically roll-back the changes.TRY...CATCH
construct has the benefit overXACT_ABORT ON
in not automatically cancelling the whole Transaction, and hence allowing the Transaction (as long asXACT_STATE()
returns1
) to be committed (even if this is an edge-case).Example of
XACT_STATE()
returning-1
whenXACT_ABORT
isOFF
:UPDATE 3
Related to item #6 in the UPDATE 2 section (i.e. possible incorrect value returned by
XACT_STATE()
when there is no active Transaction):The odd / erroneous behavior started in SQL Server 2012 (so far tested against 2012 SP2 and 2014 SP1)
In SQL Server versions 2005, 2008, and 2008 R2,
XACT_STATE()
did not report expected values when used in Triggers orINSERT...EXEC
scenarios: xact_state() cannot be used reliably to determine whether a transaction is doomed (archived page). However, in these 3 versions (I only tested on 2008 R2),XACT_STATE()
does not incorrectly report1
when used in aSELECT
with@@SPID
.There
iswas a Connect bug filed against the behavior mentioned here but is closed as "By Design": XACT_STATE() can return an incorrect transaction state in SQL 2012 (link no longer valid due to incompetent and/or grossly negligent site migration ?). However, the test was done when selecting from a DMV and it was concluded that doing so would naturally have a system generated transaction, at least for some DMVs. It was also stated in the final response by MS that:Those statements are incorrect given the following example:
Hence, I filed a new Feedback bug:
XACT_STATE() returns 1 when used in SELECT with some system variables but without FROM clause
PLEASE NOTE that in the "XACT_STATE() can return an incorrect transaction state in SQL 2012" Connect item linked directly above, Microsoft (well, a representative of) states:
However, I can find no reason to not trust
@@TRANCOUNT
. The following test shows that@@TRANCOUNT
does indeed return1
in an auto-commit transaction:I also tested on a real table with a Trigger and
@@TRANCOUNT
within the Trigger did accurately report1
even though no explicit Transaction had been started.I would approach this differently.
XACT_ABORT_ON
is a sledge hammer, you can use a more refined approach, see Exception handling and nested transactions:This approach will rollback, when possible, only the work performed inside the TRY block, and restore state to the state before entering the TRY block. This way you can do complex processing, like iterating a cursor, w/o loosing all the work in case of an error. The only draw back is that, by using transaction savepoints, you are restricted from using anything that is incompatible with savepoints, like distributed transactions.
防御性编程要求您编写代码来处理尽可能多的已知状态,从而减少出现错误的可能性。
检查 XACT_STATE() 以确定是否可以执行回滚只是一个好习惯。盲目地尝试回滚意味着您可能会无意中在 TRY...CATCH 中导致错误。
回滚可能会在 TRY...CATCH 中失败的一种方式是,如果您没有明确启动事务。复制和粘贴代码块可能很容易导致这种情况。