发生这种情况是因为未包含在TRY
/中的运行时语法错误CATCH
不会中止活动事务,即使XACT_ABORT
设置为ON
. 什么中止和不中止以及在什么情况下的确切规则一点也不简单或明显。Erland Sommarskog对一般的错误处理以及具体的中止和不中止的规则有一篇出色的文章。
我不会在这里重现所有这些,但这里的问题归结为它的要点:
SET XACT_ABORT ON -- or OFF, it makes no difference
BEGIN TRANSACTION
EXEC ('SELECT') -- Incorrect syntax near 'SELECT'
PRINT @@TRANCOUNT -- Prints 1, transaction is still going
COMMIT
PRINT @@TRANCOUNT -- Prints 0, transaction succeeded
尽管如此XACT_ABORT ON
,执行不仅没有停止,交易甚至没有中止。添加TRY
/CATCH
更改规则:
SET XACT_ABORT ON
BEGIN TRANSACTION
BEGIN TRY
EXEC ('SELECT') -- Incorrect syntax near 'SELECT'
PRINT 'After bad statement.' -- Does not print
COMMIT
END TRY
BEGIN CATCH
PRINT @@TRANCOUNT -- Prints 1, transaction is still going, but it's doomed
END CATCH
-- Error here:
-- 'Uncommittable transaction is detected at the end of the batch.
-- The transaction is rolled back.'
现在事务注定要失败,如果我们不自己回滚它,SQL Server 会为我们做这件事(有错误)。这种厄运完全是由 提供的XACT_ABORT
,因为关闭它会再次产生不同的东西:
SET XACT_ABORT OFF
BEGIN TRANSACTION
BEGIN TRY
EXEC ('SELECT') -- Incorrect syntax near 'SELECT'
PRINT 'After bad statement.' -- Does not print
COMMIT
END TRY
BEGIN CATCH
PRINT @@TRANCOUNT -- Prints 1, transaction is still going
END CATCH
PRINT @@TRANCOUNT -- Prints 1, transaction is still going!
ROLLBACK
这个故事的寓意是:在 T-SQL 中正确的错误处理是非常棘手的。通常对我有用的是SET XACT_ABORT ON
对任何重要的语句批处理执行操作,并在 SQL Server 之外完全启动和提交或回滚事务(通过客户端代码)。这避免了理解什么会暂停或终止事务的许多困难,因为 SQL Server 传递回客户端的任何错误最终都会导致回滚。但是,当然,即使这样也不是灵丹妙药。