web-dev-qa-db-ja.com

try..catch ..の使用中にSQLトランザクションをコミットできません。なぜですか?

ここで問題が発生していて、なぜそれがどのように動作するのか理解できません。

TSQL(SQL Server 2008R2)で次の2つの(簡略化された)ストアドプロシージャがあるとします。

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つの呼び出しがすべて実行され、最初と最後の(予想どおり)が正しく実行され、2番目の呼び出しがCATCHブロックに入ることがわかります。 datetransaction2内。

ここまでは順調ですね。

しかし、その後、トランザクションがコミットできないというメッセージとともにdatetransaction1のc​​atchブロックに到達します。

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.

これは起こるはずがない(私は思う)。サブプロシージャでエラーを検出したのに、なぜトランザクションが突然コミットできなくなるのでしょうか。

誰かが私にそれを説明できますか?

おそらくこれを回避する方法を見つけることができることに注意してください、しかし私はその背後にある考えにもっと興味をそそられます。なぜこのトランザクションはここで突然コミットできなくなるのですか?

13
bartlaarhoven

理由は次のとおりです。SQLServerは、エラーが発生するたびに、エラーがTRYブロックにあるかどうか、トランザクション状態を保存したかどうか、プロシージャでエラーが発生したかどうかなど、エラーが発生したときにトランザクションを停止します。行う。

プロシージャ呼び出しの1つでエラーが発生すると、トランザクションは運命づけられます。完全にロールバックすることしかできません(どのセーブポイントも役に立ちません)。

最後に、トランザクションが運命づけられているので、あなたはそれをコミットすることはできません...

これを試して:

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  
16
Jean.C

Datetransaction2関数への2回目の呼び出しにより、重大度レベル16のエラーが発生したため、SQLServerはトランザクションを自動的にロールバックしました。それがあなたが見ているエラーの理由です。

これが 本当に素晴らしい記事 重大度レベル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
3
Vasanth

つまり、catchステートメントはロールバックを引き起こすことがよくあります( 1 を参照)。これはXACT_ABORTに依存します。次に、ロールバックは、開始されたSP)には含まれません( 2 を参照)。

最初の参照( 1 )は、@@trancountを使用した回避策を示しています。そこで受け入れられている回答を参照してください。

1
DdW

コードがcatchブロックにジャンプするため、「committransaction」に到達しないようです。これを回避するには、次のように「ロールバックトランザクション」を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
1
VanDerNorth

この特定のエラーは、Try/Catchが使用されている場合にのみ発生する可能性があると確信しています。

最終的に、そのエラーは、トランザクションが開始され、自動的にロールバックを引き起こさないエラーが発生したことを意味します。考えられる理由はたくさんあります。オンになっているxact_abort設定(デフォルトではオフ)は1つだけです。自動的にロールバックされなかったエラーをキャッチし、トランザクションを自分でロールバックしませんでした。

ロールバックが必要なエラーと、自分でトランザクションを開始するかどうかを個人的に理解するのではなく、次のコードをすべてのキャッチブロックに配置します。

IF @@TRANCOUNT > 0 ROLLBACK TRANSACTION;

これにより、データが他の方法で期待するとおりに動作することを保証しながら、問題を確実に防ぐことができます。 IOWは常にエラー時にロールバックします。これを一貫して行うことで、プロシージャの呼び出し元がトランザクションを開始したか、トランザクションを開始したか、または呼び出したプロシージャがトランザクションを開始してぶら下がったままにしているかは関係ありません。エラーが発生すると、常にロールバックされます。

0
bielawski