web-dev-qa-db-ja.com

TRY CATCH ROLLBACKパターンを含むネストされたストアドプロシージャ

次のパターンの副作用と潜在的な問題に興味があります。

CREATE PROCEDURE [Name]
AS
BEGIN
    BEGIN TRANSACTION
    BEGIN TRY
        [...Perform work, call nested procedures...]
    END TRY
    BEGIN CATCH
        ROLLBACK TRANSACTION
        RAISERROR [rethrow caught error using @ErrorNumber, @ErrorMessage, etc]
    END CATCH
END

私の理解する限りでは、このパターンは単一の手順で使用すると健全です。手順はすべてのステートメントをエラーなしで完了するか、すべてのアクションをロールバックしてエラーを報告します。

ただし、あるストアドプロシージャが別のストアドプロシージャを呼び出して、作業のサブユニットを実行すると(小さなプロシージャが単独で呼び出されることもあることを理解して)、ロールバックに関連する問題が発生します-情報メッセージ(レベル16) The ROLLBACK TRANSACTION request has no corresponding BEGIN TRANSACTION.。これは、サブプロシージャで開始されたトランザクションだけでなく、サブプロシージャでのロールバックが常に最も外側のトランザクションをロールバックしているためだと思われます。

エラーが発生した場合(およびエラーがクライアントにSQLエラーとして報告された場合)、すべてをロールバックして中止します。トランザクションをロールバックしようとする外側のレイヤーから生じるすべての副作用についてはわかりません。それはすでにロールバックされています。おそらく@@TRANCOUNT各TRY CATCHレイヤーでロールバックを行う前に?

最後に、独自のトランザクション層を持つクライアントエンド(Linq2SQL)があります。

try
{
    var context = new MyDataContext();
    using (var transaction = new TransactionScope())
    {       
            // Some Linq stuff
        context.SubmitChanges();
        context.MyStoredProcedure();
        transactionComplete();
    }
}
catch
{
    // An error occured!
}

inside MyStoredProcedureと呼ばれるストアドプロシージャ「MySubProcedure」がエラーを発生させた場合、MyStoredProcedureで以前に行われたすべてがロールバックされ、SubmitChangesによって行われたすべてのLinq操作が確実になります。ロールバックし、最後にエラーがログに記録されることを?または、子パーツを個別に使用できるようにしながら、操作全体をアトミックにするためにパターンを変更する必要があります(つまり、サブプロシージャは同じアトミック保護を保持する必要があります)

53
David

これがテンプレートです(エラーログは削除されます)

これは処理するように設計されています

説明:

  • すべてのTXN開始とコミット/ロールバックは、@@TRANCOUNTが入り口と出口で同じになるようにペアにする必要があります

  • @@TRANCOUNTの不一致はエラー266を引き起こします

    • BEGIN TRANインクリメント@@TRANCOUNT

    • COMMIT減少@@TRANCOUNT

    • ROLLBACK@@TRANCOUNTをゼロに戻します

  • 現在のスコープに対して@@TRANCOUNTをデクリメントすることはできません
    これは「内部取引」だと思われます

  • SET XACT_ABORT ONは、@@TRANCOUNTの不一致によるエラー266を抑制します
    そして、このような問題も扱います "SQL Server Transaction Timeout" dba.se

  • これにより、クライアント側のTXN(LINQなど)が可能になります。単一のストアドプロシージャは、分散トランザクションまたはXAトランザクションの一部、または単にクライアントコード(.net TransactionScopeなど)で開始されたものです。

使用法:

  • 各ストアドプロシージャは同じテンプレートに準拠する必要があります

概要

  • 必要以上にTXNを作成しないでください

コード

CREATE PROCEDURE [Name]
AS
SET XACT_ABORT, NOCOUNT ON

DECLARE @starttrancount int

BEGIN TRY
    SELECT @starttrancount = @@TRANCOUNT

    IF @starttrancount = 0
        BEGIN TRANSACTION

       [...Perform work, call nested procedures...]

    IF @starttrancount = 0 
        COMMIT TRANSACTION
END TRY
BEGIN CATCH
    IF XACT_STATE() <> 0 AND @starttrancount = 0 
        ROLLBACK TRANSACTION;
    THROW;
    --before SQL Server 2012 use 
    --RAISERROR [rethrow caught error using @ErrorNumber, @ErrorMessage, etc]
END CATCH
GO

注:

  • SET XACT_ABORT ONのため、ロールバックチェックは実際には冗長です。しかし、それは私を気分良くさせ、なしで奇妙に見え、あなたがそれを望まない状況を可能にします

  • Remus Rusan には、セーブポイントを使用する similar Shell があります。私はアトミックDBコールを好み、彼らの記事のような部分的な更新を使用しません

106
gbn

私はLinqの男ではありません(Erlandでもありません)が、エラー処理に関する絶対的な聖書を書いています。 Linqが問題を悪化させる可能性のある複雑さ以外にも、他のすべての質問に回答する必要があります。

http://www.sommarskog.se/error_handling/Part1.html

(古いリンク: http://www.sommarskog.se/error_handling_2005.html

10
Aaron Bertrand

@AlexKuznetsovで言及されているエラー番号と行番号を返す問題を解決するには、次のようにエラーを発生させることができます。

DECLARE @ErrorMessage NVARCHAR(4000)
DECLARE @ErrorSeverity INT
DECLARE @ErrorState INT
DECLARE @ErrorLine INT
DECLARE @ErrorNumber INT

SELECT @ErrorMessage = ERROR_MESSAGE(),
@ErrorSeverity = ERROR_SEVERITY(),
@ErrorState = ERROR_STATE(),
@ErrorNumber = ERROR_NUMBER(),
@ErrorLine = ERROR_LINE()

RAISERROR (@ErrorMessage, @ErrorSeverity, @ErrorState, @ErrorNumber, @ErrorLine)
1
Amanda

-上記の@Amandaメソッドは正しいエラー番号を返しません

DECLARE  
  @ErrorMessage   nvarchar(4000),  
  @ErrorSeverity   int,  
  @ErrorState int,  
  @ErrorLine  int,  
  @ErrorNumber   int  

BEGIN TRY  
 SELECT 1/0; -- CATCH me  
END TRY  

BEGIN CATCH  

  DECLARE @err int = @@ERROR  

  PRINT @err           -- 8134, divide by zero  
  PRINT ERROR_NUMBER() -- 8134  

  SELECT  
    @ErrorMessage  = ERROR_MESSAGE(),  
    @ErrorSeverity = ERROR_SEVERITY(),  
    @ErrorState    = ERROR_STATE(),  
    @ErrorNumber   = ERROR_NUMBER(),  
    @ErrorLine     = ERROR_LINE()  

  -- error number = 50000 :(  
  RAISERROR (@ErrorMessage, @ErrorSeverity, @ErrorState, @ErrorNumber, @ErrorLine)  

END CATCH  

-- error number = 8134  
SELECT 1/0
0
Ben Tennen