13

ここで問題が発生していますが、なぜこのように動作するのかわかりません。

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

ここまでは順調ですね。

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.

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

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

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

4

5 に答える 5

17

その理由は次のとおりです。SQL Server は、エラーが発生するたびに、エラーが 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  
于 2014-02-21T14:48:18.703 に答える
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 に答える