当 TRY 块中出现错误时,我对使用 XACT_ABORT ON 和 TRY...CATCH 构造来尝试回滚 CATCH 块中的事务感到有些困惑。
我有一个像这样结构化的存储过程(当然在这里简化了):
CREATE PROCEDURE dbo.usp_clean_and_re_Insert
AS
SET XACT_ABORT ON;
BEGIN TRY
BEGIN TRANSACTION
-- first clear the table
DELETE FROM dbo.table1
-- re-populate the table
INSERT INTO dbo.table1
(col1, col2, col3)
SELECT 1
,dbo.fn_DoSomething('20150101')
,dbo.fn_DoSomething('20150123')
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;
所以 SP 的目的是这样工作:如果事务在任何时候失败,它应该回滚。所以当插入位失败时,删除位应该回滚,即表应该和之前一样的状态。
现在,假设在运行时 dbo.fn_DoSomething() 函数不可用(它已被 DBA 错误地删除)。上面写的 SP 按预期工作,即事务回滚,表保持不变,SSMS 中显示的错误消息如下所示:
“消息 208,级别 16,状态 1,过程 usp_clean_and_re_Insert,第 15 行无效对象名称 'dbo.fn_DoSOmething'。 ”
但是由于某种原因,来自 CATCH 块的 PRINT 语句似乎没有执行,即我在 SSMS 中看不到它们?关于 TRY...CATCH 的 Microsoft 文档说,如果在 TRY 块中执行期间发生错误,则执行将传递给 CATCH 块(https://msdn.microsoft.com/en-us/library/ms175976(v=sql .90).aspx )。
但是,如果我删除 XACT_ABORT ON,事情会变得更加奇怪:
PRINT 语句仍然没有出现在 SSMS 中
与上面相同的错误正确显示,即
“消息 208,级别 16,状态 1,过程 usp_clean_and_re_Insert,第 15 行无效对象名称 'dbo.fn_DoSOmething'。 ”
- 有一个最后的错误说:
“消息 266,级别 16,状态 2,过程 usp_clean_and_re_Insert,第 52 行 EXECUTE 后的事务计数表明缺少 COMMIT 或 ROLLBACK TRANSACTION 语句。先前计数 = 0,当前计数 = 1。”
这导致表被锁定,直到我断开 SSMS(SP 运行的查询窗口),之后表再次可用,所有结果都完好无损(因此数据库引擎必须隐式回滚不可提交的事务)。
阅读有关此错误消息的其他帖子(例如这个:EXECUTE 后的事务计数表示 BEGIN 和 COMMIT 语句的数量不匹配。以前的计数 = 1,当前计数 = 0),我知道我需要检查 CATCH 块中的 XACT_STATE并回滚不可提交的事务(这与来自:https ://msdn.microsoft.com/en-us/library/ms189797.aspx 的建议相同),但这正是我在上述 SP 中所做的,但事务在我断开 SSMS 之前不会回滚(没有 XACT_ABORT ON)?
我很困惑!总之:
为什么我在 SSMS 中看不到 PRINT 语句?
为什么从存储过程中删除 XACT_ABORT ON 时,CATCH 块中的 ROLLBACK TRANSACTION 不执行?
如果 XACT_ABORT ON 似乎可以自己完成这项工作,为什么还要使用 TRY...CACTH?即,如果我删除 Try..catch 并保留 XACT_ABORT ,它会回滚事务,那么为什么我需要在 catch 块中使用隐式 ROLLBACK TRANSACTION 的 TRY CATCH 呢?