使用 try..catch.. 时 SQL 事务不可提交。为什么?

标签 sql sql-server transactions sql-server-2008-r2 try-catch

我们在这里遇到了一个问题,我无法弄清楚它为什么会这样。

给定 TSQL (SQL Server 2008R2) 中的以下两个(简化的)存储过程

create procedure [datetransaction1] 
as
begin
    begin try
        begin transaction
        declare @a datetime
        exec datetransaction2 '2013-02-02 22:21', @a output
        select @a
        exec datetransaction2 '2013-020222:22', @a output
        select @a
        exec datetransaction2 '2013-02-02 22:23', @a output
        select @a

        commit transaction
    end try
    begin catch
        print 'Catch'
    end catch
end

create procedure [dbo].[datetransaction2] @text nvarchar(100), @res datetime OUTPUT  
AS
BEGIN 
    BEGIN TRY
        if (LEN(@text) = 16) SET @text = replace(@text, ' ', 'T') + ':00.000'
        else if (LEN(@text) = 19) SET @text = replace(@text, ' ', 'T') + '.000'
        else SET @text = replace(@text, ' ', 'T') 
        PRINT 'trydate:' + @text
        SELECT @res =convert(datetime, @text, 126)
    END TRY
    BEGIN CATCH
        PRINT ERROR_SEVERITY()
        PRINT 'errordate:' + @text
    END CATCH
END

如果您随后执行 exec datetransaction1,我们会看到对 datetransaction2 的所有 3 个调用均已执行,第一个和最后一个(如预期)运行正确,第二个调用运行正确输入 datetransaction2 中的 CATCH block 。

到目前为止,一切顺利。

但是随后我们进入了 datetransaction1 的 catch block ,并显示该事务不可提交的消息:

Msg 266, Level 16, State 2, Procedure datetransaction1, Line 0
Transaction count after EXECUTE indicates a mismatching number of BEGIN and COMMIT statements. Previous count = 0, current count = 1.
Msg 3998, Level 16, State 1, Line 1
Uncommittable transaction is detected at the end of the batch. The transaction is rolled back.

这不应该发生(我认为)。我们在子过程中发现了错误,那么为什么事务会突然变得不可提交呢?

有人可以向我解释一下吗?

请注意,我们可能可以找到解决此问题的方法,但我对它背后的想法更感兴趣。为什么这个交易在这里突然变得不可提交?

最佳答案

原因是:只要发生错误,无论错误是什么,无论是否在 TRY block 中,无论是否保存事务状态,无论错误是否发生在过程中,SQL Server 都会注定事务失败,无论你做什么。

当其中一个过程调用发生错误时,事务就注定失败。您只能完全回滚它(任何保存点都无济于事)。

最后,由于事务注定失败,因此您无法提交它...

试试这个:

SET XACT_ABORT OFF -- pityful attempt to avoid the doom
BEGIN TRANSACTION
--
-- some useful TSQL instructions could be here
--
SAVE TRANSACTION SQL_SERVER_IS_GARBAGE -- another pityful attempt to do a partial restore
PRINT 'XACT_STATE='+CONVERT(varchar(10),XACT_STATE())
BEGIN TRY
  DECLARE @n int
  SELECT @n = CONVERT(int,'ABC') -- some very benign data error here (example)
  COMMIT TRANSACTION -- will never reach here
END TRY
BEGIN CATCH
  PRINT ERROR_MESSAGE()
  PRINT 'XACT_STATE='+CONVERT(varchar(10),XACT_STATE())
  IF XACT_STATE()=-1 BEGIN
    PRINT 'The transaction is doomed, say thanks to Sql Server!'
    PRINT 'CANNOT restore to the save point!'
    -- You can just cry here and abort all, you lost all the useful work
    ROLLBACK TRANSACTION
  END
  ELSE BEGIN
    -- would restore before the error if the transaction was not doomed
    ROLLBACK TRANSACTION SQL_SERVER_IS_GARBAGE -- will never reach here either!
  END  
END CATCH  

关于使用 try..catch.. 时 SQL 事务不可提交。为什么?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/18787678/

相关文章:

数据库自动提交 - 它直接进入磁盘吗?

sql - Sequelize 过滤器嵌套包含

sql - 分析函数可以引用窗口中的多行吗?

sql-server - 将服务器添加到 SQL Management Studio

sql-server - 如何使用 T-SQL 判断 SQL Server 数据库的 TRUSTWORTHY 属性是否设置为打开或关闭

PostgreSQL for 更新语句

sql - 同步客户端-服务器数据库

php - 多表选择

sql-server - 如何在 SqlServer Compact 数据库上执行 Entity Framework 模型第一个 DDL 文件?

java - Spring Transaction 尝试在选择查询后触发更新