13

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

给定 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,我们会看到所有 3 个调用datetransaction2都已执行,第一个和最后一个(如预期的那样)正确运行,第二个CATCH进入datetransaction2.

到现在为止还挺好。

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

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.

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

有人可以向我解释吗?

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

4

5 回答 5

17

原因是:SQL Server 在发生错误时终止事务,无论错误是什么,是否在 TRY 块中,是否保存了事务状态,错误是否发生在过程中,无论你做。

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

最后,由于交易注定要失败,你不能提交它......

试试这个:

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  
于 2014-02-21T14:48:18.703 回答
3

由于第二次调用 datetransaction2 函数导致严重级别为 16 的错误,SQL Server 自动回滚了您的事务。这就是您看到错误的原因。

这是一篇非常好的文章,为什么在发生严重级别 16 错误时事务会进入注定状态。

为了验证它是否自动回滚,我在您的 datetransaction2 proc 中添加了以下行: print XACT_STATE()

  create procedure [dbo].[datetransaction2] @text nvarchar(100), @res datetime OUTPUT  
  AS
  BEGIN 
     print 'Start'
      print XACT_STATE() 
      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 XACT_STATE() 
           print 'Catch'
          PRINT ERROR_SEVERITY()
          PRINT 'errordate:' + @text
      END CATCH
      print XACT_STATE() 
      print 'End'
  END
于 2013-09-13T14:39:11.697 回答
1

看起来永远不会到达“提交事务”,因为代码跳转到了 catch 块。为了避免这种情况,你可以像这样在你的 catch 块中添加一个“回滚事务”:

alter 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'
         rollback transaction
    end catch
end
于 2013-09-13T13:48:30.937 回答
1

简而言之:一条catch语句通常会导致回滚(参见1)。这取决于XACT_ABORT. 接下来,回滚不包含在启动它们的 SP 中(参见2)。

第一个参考 ( 1 ) 给出了一种解决方法@@trancount,请参见那里接受的答案。

于 2013-09-13T21:58:21.267 回答
0

我很确定只有在使用 Try/Catch 时才会发生此特定错误。

最终,该错误意味着事务已启动并且发生的错误不会自动导致回滚。有很多可能的原因。开启的 xact_abort 设置(默认关闭)只有一个。您发现了一个不会自动回滚的错误,并且您自己也没有回滚事务。

我没有了解哪些错误需要回滚以及何时我自己是否启动事务,我将以下代码放在每个 catch 块中。

IF @@TRANCOUNT > 0 ROLLBACK TRANSACTION;

这绝对可以防止问题发生,同时确保您的数据完全按照您的预期方式运行。IOW 总是在出错时回滚。始终如一地执行此操作可确保您的过程的某个调用者启动一个事务,您启动一​​个事务,或者您调用的某个过程启动一个事务并使其悬空都无关紧要;当您发现错误时,它总是回滚。

于 2019-06-20T18:57:24.793 回答