使用try..catch时,SQL Transaction无法提供..为什么?

时间:2013-09-13 13:33:40

标签: 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次调用都已执行,第一次和最后一次(正如预期)正常运行,第二次进入CATCHdatetransaction2内阻止。

到目前为止,非常好。

但随后我们登陆datetransaction1的catch区块,并发出交易无法提交的消息:

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.

不应发生这种情况(我认为)。我们在子程序中发现了错误,为什么交易突然变得无法承受?

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

请注意,我们可能会找到解决此问题的方法,但我对其背后的想法更感兴趣。为什么这笔交易突然变得无法承受?

5 个答案:

答案 0 :(得分:15)

原因是:无论错误发生在什么位置,无论是否在TRY块中,是否保存了事务状态,是否在程序中发生错误,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  

答案 1 :(得分:3)

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

以下是really nice article当发生严重级别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

答案 2 :(得分:1)

看起来永远不会达到'commit transaction',因为代码会跳转到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

答案 3 :(得分:1)

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

第一个引用(1)使用@@trancount提供了一种解决方法,请参阅那里接受的答案。

答案 4 :(得分:0)

我敢肯定,使用“尝试/捕获”功能只会发生此特定错误。

最终,该错误表示已启动事务,并且发生了不会自动导致回滚的错误。当前xact_abort设置(默认情况下处于禁用状态)仅为1的原因可能有很多。您发现了一个不会自动回滚的错误,也没有自己回滚该事务。

我不了解什么错误需要回滚,以及什么时候亲自(无论我自己发起还是不发起交易)将以下代码放在每个catch块的开头。

IF @@TRANCOUNT > 0 ROLLBACK TRANSACTION;

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