TL; DR /执行摘要:关于问题的这一部分:
我没有在什么情况下,控制里面可以看到通过CATCH
与可提交的事务时,XACT_ABORT
被设置为ON
。
我现在已经对此进行了大量测试,并且找不到当when 和 session属性为is 时在块内XACT_STATE()
返回的任何情况。实际上,根据SET XACT_ABORT的当前MSDN页面:1
CATCH
@@TRANCOUNT > 0
XACT_ABORT
ON
当SET XACT_ABORT为ON时,如果Transact-SQL语句引发运行时错误,则整个事务将终止并回滚。
该声明似乎与您的推测和我的发现一致。
关于MSDN的文章SET XACT_ABORT
提供了一个示例,其中将事务中的某些语句成功执行而将某些语句XACT_ABORT
设置为则失败OFF
没错,但在该例子语句是不是一个内TRY
块。TRY
块中的那些相同语句仍会阻止执行引起错误的语句之后的任何语句,但是假设XACT_ABORT
是OFF
,当控制权传递给该CATCH
块时,Transaction在物理上仍然有效,因为所有先前更改均已发生而没有错误并且可以被提交,如果是这样的愿望,或者他们可以回滚。另一方面,如果XACT_ABORT
是,ON
则任何先前的更改都会自动回滚,然后您可以选择以下一种:a)发出aROLLBACK
这大部分只是对情况的接受,因为交易已经回滚,减去了重置@@TRANCOUNT
为0
,或b)收到错误。没有太多选择,是吗?
对于这个难题,一个可能不重要的细节(在该文档中找不到)是SET XACT_ABORT
自SQL Server 2000以来,此会话属性甚至该示例代码就已经存在(两个版本之间的文档几乎相同),早于该TRY...CATCH
结构。在SQL Server 2005中引入再次是看文档和例子看(没有的TRY...CATCH
),使用XACT_ABORT ON
原因进行即时交易的回滚:有“不可提交”(请注意,无交易状态,没有提及在该SET XACT_ABORT
文档中所有“不可提交的”交易状态)。
我认为得出以下结论是合理的:
TRY...CATCH
SQL Server 2005 中该结构的引入产生了对新事务状态(即“不可提交”)的需求以及XACT_STATE()
获取该信息的功能。
- 如果同时满足以下两个条件,则仅
XACT_STATE()
在CATCH
块中检查才有意义:
XACT_ABORT
是OFF
(否则XACT_STATE()
应该总是返回-1
并且@@TRANCOUNT
将是您所需要的)
- 您可以在代码
CATCH
块中,或者在调用嵌套的情况下,在代码链中某个位置进行逻辑处理,而不是执行操作即可进行更改(a COMMIT
或什至任何DML,DDL等语句)。(这是一个非常不典型的用例)**请参阅UPDATE 3部分底部的注释,该注释关于Microsoft始终建议而不是进行检查的非官方建议,以及为什么测试表明他们的推理没有成功。ROLLBACK
XACT_STATE()
@@TRANCOUNT
TRY...CATCH
在SQL Server 2005中,该构造的引入在大多数情况下已废除了XACT_ABORT ON
session属性,因为它提供了对Transaction的更大程度的控制(您至少可以选择COMMIT
,前提是XACT_STATE()
不返回-1
)。
另一种方式来看待,这是,之前到SQL Server 2005,XACT_ABORT ON
提供了一个方便,可靠的方式来停止处理时发生错误,较检查@@ERROR
每个语句后。
- 为文档例如代码
XACT_STATE()
是错误的,或充其量误导,因为它显示了检查XACT_STATE() = 1
时XACT_ABORT
是ON
。
较长的部分;-)
是的,MSDN上的示例代码有点令人困惑(另请参阅:@@ TRANCOUNT(回滚)与XACT_STATE);-)。而且,我觉得这是误导,因为它要么显示的东西,是没有意义的(对于您所问的原因:你能即使在具有“可提交”交易CATCH
时,块XACT_ABORT
是ON
),甚至如果可能的话,它仍然专注于很少有人会想要或不需要的技术可能性,而忽略了人们更可能需要它的原因。
如果TRY块内有足够严重的错误,则控件将传递到CATCH中。因此,如果我在CATCH内,我知道交易有问题,实际上在这种情况下唯一明智的做法是将其回滚,不是吗?
我认为,如果我们确保某些单词和概念的含义在同一页上,将会有所帮助:
“严重程度足够高的错误”:明确起见,TRY ... CATCH将捕获大多数错误。在该链接的MSDN页面上的“不受TRY…CATCH构造影响的错误”部分下,列出了不会捕获的内容。
“如果我在CATCH内,我知道事务有问题”(添加了pha):如果通过“事务”是指由您将语句分组为显式事务确定的逻辑工作单元,则很可能是。我认为我们大多数数据库专家都会同意回滚是“唯一明智的做法”,因为我们可能对如何以及为何使用显式事务有相似的看法,并设想应该组成原子单元的步骤工作的。
但是,如果您的意思是将实际的工作单元分组到显式事务中,那么不,您不知道事务本身存在问题。您只知道在显式定义的事务中执行的语句引发了错误。但这可能不是DML或DDL语句。即使是DML语句,事务本身也可能是可提交的。
鉴于以上两点,我们可能应该在您“不能”提交的事务和您“不想”提交的事务之间进行区分。
当XACT_STATE()
返回1
时,表示事务是“可提交的”,您可以在COMMIT
或之间进行选择ROLLBACK
。您可能不想提交它,但是如果出于某些原因甚至很难举一个例子,您至少可以这样做,因为事务的某些部分确实成功完成了。
但是,当XACT_STATE()
返回a时-1
,您确实需要这样做,ROLLBACK
因为Transaction的某些部分进入了不良状态。现在,我确实同意,如果控制权已经传递给CATCH块,那么只需进行检查就足够了@@TRANCOUNT
,因为即使您可以提交事务,为什么还要这么做?
但是,如果您在示例的顶部注意到,则XACT_ABORT ON
更改设置会有所改变。您可能会遇到一个常规错误,执行BEGIN TRAN
此操作后,将在XACT_ABORT
is OFF
结束时将控制权传递给CATCH块,并且XACT_STATE()将返回1
。但是,如果XACT_ABORT为ON
,则由于任何'ol错误,事务将“中止”(即无效),然后XACT_STATE()
将返回-1
。在这种情况下,似乎没用检查XACT_STATE()
的内CATCH
块,因为它似乎总是返回-1
时XACT_ABORT
是ON
。
那么那是XACT_STATE()
为了什么呢?一些线索是:
TRY...CATCH
在“不可提交的事务和XACT_STATE”部分下的MSDN页面上,该页面显示:
通常,在TRY块外部结束事务的错误会导致事务在TRY块内部发生错误时进入不可提交状态。
SET XACT_ABORT的MSDN页面在“备注”部分下显示:
当SET XACT_ABORT为OFF时,在某些情况下,仅回滚引发错误的Transact-SQL语句,并且事务将继续处理。
和:
必须针对大多数OLE DB提供程序(包括SQL Server)在隐式或显式事务中为数据修改语句将XACT_ABORT设置为ON。
BEGIN TRANSACTION的MSDN页面的“备注”部分下显示:
如果在提交或回滚该语句之前执行了以下操作,则由BEGIN TRANSACTION语句启动的本地事务将升级为分布式事务:
- 执行引用链接服务器上的远程表的INSERT,DELETE或UPDATE语句。如果用于访问链接服务器的OLE DB提供程序不支持ITransactionJoin接口,则INSERT,UPDATE或DELETE语句将失败。
最适用的用法似乎是在Linked Server 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()
:
SET XACT_ABORT OFF;
BEGIN TRY
BEGIN TRAN;
SELECT 1/0 AS [DivideByZero]; -- error, yo!
COMMIT TRAN;
END TRY
BEGIN CATCH
SELECT @@TRANCOUNT AS [@@TRANCOUNT],
XACT_STATE() AS [XactState],
ERROR_MESSAGE() AS [ErrorMessage]
IF (@@TRANCOUNT > 0)
BEGIN
ROLLBACK;
END;
END CATCH;
GO ------------------------------------------------
SET XACT_ABORT ON;
BEGIN TRY
BEGIN TRAN;
SELECT 1/0 AS [DivideByZero]; -- error, yo!
COMMIT TRAN;
END TRY
BEGIN CATCH
SELECT @@TRANCOUNT AS [@@TRANCOUNT],
XACT_STATE() AS [XactState],
ERROR_MESSAGE() AS [ErrorMessage]
IF (@@TRANCOUNT > 0)
BEGIN
ROLLBACK;
END;
END CATCH;
GO ------------------------------------------------
SET XACT_ABORT ON;
BEGIN TRY
SELECT 1/0 AS [DivideByZero]; -- error, yo!
END TRY
BEGIN CATCH
SELECT @@TRANCOUNT AS [@@TRANCOUNT],
XACT_STATE() AS [XactState],
ERROR_MESSAGE() AS [ErrorMessage]
END CATCH;
更新
虽然不是原始问题的一部分,但基于此答案的以下评论:
我一直在阅读Erland关于错误和事务处理的文章,他说这XACT_ABORT
是OFF
出于遗留原因的默认设置,通常我们应该将其设置为ON
。
...
“ ...如果您遵循建议并在SET XACT_ABORT ON上运行,则该事务将永远失败。
在XACT_ABORT ON
到处使用之前,我会问:这里到底获得了什么?我认为没有必要这样做,并且通常主张您仅在必要时使用它。ROLLBACK
通过使用@Remus的答案中显示的模板,或者我多年来使用的模板,基本上都一样,但是没有保存点,您是否想要足够容易地处理,如本答案所示(处理嵌套的调用):
我们是否需要使用C#代码以及存储过程来处理事务
更新2
我做了一些测试,这次是创建一个小的.NET Console应用程序,在执行任何SqlCommand
对象之前(即通过using (SqlTransaction _Tran = _Connection.BeginTransaction()) { ...
)在应用程序层中创建一个Transaction ,以及使用批中止错误而不是仅仅执行一条语句错误中止,发现:
- 大多数情况下,“不可提交的”事务已经回滚(更改已撤消),但
@@TRANCOUNT
仍大于0。
- 当您有“不可提交的”交易时,您将无法发出,
COMMIT
因为这将生成错误消息,指出该交易是“ 不可提交的”。您也不能忽略它/不执行任何操作,因为当该批处理结束时,将指出该批处理完成了无法解决的事务,并且会回滚(因此,嗯,如果它仍会自动回滚,为什么要麻烦抛出错误?)。因此,您必须在批处理结束之前发出一个explicit ROLLBACK
,也许不是在立即CATCH
块中。
- 在
TRY...CATCH
结构中,当XACT_ABORT
为is时OFF
,如果错误发生在TRY
块外,则将自动终止事务的错误(例如,中止错误)将撤消工作,但不会终止Tranasction,将其保留为“不可提交”。签发a ROLLBACK
只是完成交易所需的手续,但工作已被回滚。
- 当
XACT_ABORT
是ON
,大多数错误作为分批中止,并作为直接在上面(#3)在项目符号点描述因此表现。
XACT_STATE()
,至少在一个CATCH
块中,-1
如果在错误发生时有活动的交易,则将显示批中止错误。
XACT_STATE()
有时1
即使没有活动的交易也会返回。如果@@SPID
(以及其他)在SELECT
列表中XACT_STATE()
,则XACT_STATE()
在没有活动的Transaction时将返回1。此行为始于SQL Server 2012,并于2014年存在,但我尚未在2016年进行测试。
考虑到以上几点:
- 鉴于分#4,#5,因为大部分(或全部?)错误将呈现交易“uncommitable”,似乎完全没有意义的检查
XACT_STATE()
中CATCH
块的时候XACT_ABORT
是ON
因为返回的永远是值-1
。
- 检查
XACT_STATE()
中CATCH
块时XACT_ABORT
是OFF
更有意义,因为返回值将至少有一些变化,因为它会返回1
的语句中止错误。但是,如果您像我们大多数人一样进行编码,则这种区别是没有意义的,因为ROLLBACK
无论如何,您都将仅出于发生错误的事实进行调用。
- 如果发现确实令发出的情况
COMMIT
在CATCH
块,然后检查的价值XACT_STATE()
,并确保SET XACT_ABORT OFF;
。
XACT_ABORT ON
似乎并没有提供任何好处TRY...CATCH
。
- 我发现没有一种情况
XACT_STATE()
比单纯的检查能够提供有意义的好处@@TRANCOUNT
。
- 在那里我还可以找到任何情况下
XACT_STATE()
返回1
的CATCH
块时XACT_ABORT
是ON
。我认为这是文档错误。
- 是的,您可以回滚未明确开始的事务。在使用的情况下
XACT_ABORT ON
,这是有争议的,因为在TRY
块中发生的错误将自动回滚更改。
- 该
TRY...CATCH
构造的好处XACT_ABORT ON
是不会自动取消整个Transaction,因此允许提交Transaction(只要XACT_STATE()
returns 1
)(即使这是一个极端情况)。
XACT_STATE()
返回-1
when的示例XACT_ABORT
是OFF
:
SET XACT_ABORT OFF;
BEGIN TRY
BEGIN TRAN;
SELECT CONVERT(INT, 'g') AS [ConversionError];
COMMIT TRAN;
END TRY
BEGIN CATCH
DECLARE @State INT;
SET @State = XACT_STATE();
SELECT @@TRANCOUNT AS [@@TRANCOUNT],
@State AS [XactState],
ERROR_MESSAGE() AS [ErrorMessage];
IF (@@TRANCOUNT > 0)
BEGIN
SELECT 'Rollin back...' AS [Transaction];
ROLLBACK;
END;
END CATCH;
更新3
与UPDATE 2部分中的项目#6相关(即,XACT_STATE()
当没有活动Transaction时可能返回的错误值):
- 奇怪/错误的行为始于SQL Server 2012(到目前为止已针对2012 SP2和2014 SP1进行了测试)
- 在SQL Server 2005、2008和2008 R2中,
XACT_STATE()
在触发器或INSERT...EXEC
场景中使用时未报告预期值:xact_state()无法可靠地用于确定事务是否注定失败。但是,(我只在2008 R2测试)这3个版本,XACT_STATE()
并没有错误地报告1
在使用时SELECT
用@@SPID
。
有一个针对此处提到的行为的Connect错误,但已通过“按设计”关闭:XACT_STATE()可以在SQL 2012中返回错误的事务状态。但是,在从DMV中进行选择时进行了测试,并得出结论,这样做自然会产生系统生成的事务,至少对于某些DMV而言。MS在最终答复中还指出:
请注意,IF语句以及没有FROM的SELECT都不会启动事务。
例如,如果您没有以前存在的事务,则运行SELECT XACT_STATE()将返回0。
在以下示例中,这些语句是错误的:
SELECT @@TRANCOUNT AS [TRANCOUNT], XACT_STATE() AS [XACT_STATE], @@SPID AS [SPID];
GO
DECLARE @SPID INT;
SET @SPID = @@SPID;
SELECT @@TRANCOUNT AS [TRANCOUNT], XACT_STATE() AS [XACT_STATE], @SPID AS [SPID];
GO
因此,新的Connect错误:
当在SELECT中使用某些系统变量但不使用FROM子句时,XACT_STATE()返回1
请注意,在上面直接链接的“ XACT_STATE()可以在SQL 2012中返回错误的事务状态”连接项中,Microsoft(代表)表示:
@@ trancount返回BEGIN TRAN语句的数量。因此,它不是是否存在活动交易的可靠指标。如果存在活动的自动提交事务,则XACT_STATE()也会返回1,因此它是是否存在活动事务的更可靠的指示符。
但是,我找不到不信任的理由@@TRANCOUNT
。以下测试表明@@TRANCOUNT
确实确实1
在自动提交事务中返回:
--- begin setup
GO
CREATE PROCEDURE #TransactionInfo AS
SET NOCOUNT ON;
SELECT @@TRANCOUNT AS [TranCount],
XACT_STATE() AS [XactState];
GO
--- end setup
DECLARE @Test TABLE (TranCount INT, XactState INT);
SELECT * FROM @Test; -- no rows
EXEC #TransactionInfo; -- 0 for both fields
INSERT INTO @Test (TranCount, XactState)
EXEC #TransactionInfo;
SELECT * FROM @Test; -- 1 row; 1 for both fields
我还使用触发器@@TRANCOUNT
在真实表上进行了测试,1
即使没有启动显式事务,触发器内的报告也能准确报告。
XACT_ABORT
为ON
还是OFF
。