web-dev-qa-db-ja.com

SQL 2005ストアドプロシージャにエラー処理を追加する最良の方法は何ですか?

ストアドプロシージャを十分に堅牢にし、非常に適切にスケーリングし、エラー処理を含めるための良い方法は何ですか?

さらに、ストアドプロシージャで複数のエラーシナリオを処理し、呼び出し元のアプリに意味のあるエラー情報を返すインテリジェントフィードバックシステムを使用するための最良の方法は何ですか?

11
kacalapy

Alex Kuznetsovの著書Defensive Database Programming(第8章)には、T-SQL TRY ... CATCH、T-SQLトランザクションとSET XACT_ABORT設定、およびクライアント側のエラー処理の使用に関するすばらしい章があります。これは、どのオプションを実行する必要があるかについて最も意味のあるオプションを決定するのに役立ちます。

freethis site で利用できます。私はその会社とはまったく関係がありませんが、その本のハードコピー版を所有しています。

このテーマについては、アレックスが非常によく説明している細部がたくさんあります。

ニックの要求による...(しかし、このすべてが章にあるわけではありません)

スケーリングに関しては、dbコードに含める必要があるアクティビティとアプリに含める必要があるアクティビティについて、非常に正直である必要があります。高速に実行されるコードが、メソッドごとに単一の関心事の設計に戻る傾向があることに気付いたことがありますか?

通信する最も簡単な方法は、カスタムエラーコード(> 50,000)です。また、かなり高速です。それはあなたがdbコードとアプリコードを同期させておく必要があることを意味します。カスタムエラーコードを使用すると、エラーメッセージ文字列で有用な情報を返すこともできます。その状況に厳密に応じたエラーコードがあるため、エラーのデータ形式に合わせて調整されたアプリコードでパーサーを記述できます。

また、データベースで再試行ロジックが必要なエラー条件はどれですか。 X秒後に再試行する場合は、トランザクションがそれほどブロックされないように、アプリのコードでそれを処理することをお勧めします。 DML操作をすぐに再送信するだけの場合は、SPで繰り返すのが効率的です。ただし、コードを複製するか、再試行を実行するためのSPのレイヤー。

実際、それが現在SQL ServerのTRY ... CATCHロジックの最大の問題です。それはできますが、それは少し難解です。特に システム例外の再スロー (元のエラー番号を保持)で、SQL Server 2012でこれに加えられているいくつかの改善を探してください。また、 [〜#〜] formatmessage [〜#〜] があり、特にロギングの目的で、エラーメッセージを作成する際に柔軟性が追加されています。

12
Phil Helmer

これは私たちのテンプレートです(エラーログは削除されました)

ノート:

  • XACT_ABORTがない場合、すべてのTXN開始およびコミット/ロールバックをペアにする必要があります
  • コミットは@@ TRANCOUNTを減少させます
  • ロールバックは@@ TRANCOUNTをゼロに戻すため、エラー266が発生します
  • 現在のレイヤーのみをロールバックすることはできません(例:ロールバック時に@@ TRANCOUNTをデクリメント)
  • XACT_ABORTはエラー266を抑制します
  • 各ストアドプロシージャは同じテンプレートに準拠する必要があるため、各呼び出しはアトミックです
  • XACT_ABORTのため、ロールバックチェックは実際には冗長です。しかし、それは私をより気持ちよくさせ、なしで奇妙に見え、あなたがそれを望まない状況を可能にします
  • これにより、クライアント側のTXN(LINQなど)が可能になります
  • Remus Rusan には、セーブポイントを使用する similar Shell があります。私はアトミックDB呼び出しを好み、記事のような部分的な更新は使用しません

...したがって、必要以上の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
    RAISERROR [rethrow caught error using @ErrorNumber, @ErrorMessage, etc]
END CATCH
GO
7
gbn

私はTry/Catchを使用していますが、できるだけ多くの情報を収集し、ロールバック後にエラーログに書き込みます。この例では、「LogEvent」は、発生したイベントの詳細を含む、EventLogテーブルに書き込むストアドプロシージャです。 GetErrorInfo()は、正確なエラーメッセージを返す関数呼び出しです。

エラーが発生すると、情報が収集され、手順はエラー処理セクションまでスキップしてロールバックを発行します。情報がログに書き込まれた後、プロシージャは終了します。

追加のプロシージャ/関数呼び出しが含まれていることを考えると、それは少し上回っています。ただし、この方法は、問題をデバッグするときに非常に役立ちます。

 exec LogEvent @Process、@Database、 'Attinging to insert blah blah blah' 
 BEGIN TRY 
 insert into MyTable 
 select Values 
 from MyOtherTable 
 
 select @rowcount = @@ ROWCOUNT 
 END TRY 
-ErrorHandling 
 BEGIN CATCH 
 select @error = ERROR_NUMBER( )、
 @rowcount = -1、
 @TableAction = 'insert'、
 @TableName = @Database + '.MyTable'、
 @AdditionalInfo = ' (何とか何とか何とか挿入しようとしています) '+ dbo.GetErrorInfo()
 GOTO TableAccessError 
 END CATCH 
 
。
。
。
。
 
 TableAccessError:
 IF(@@ TRANCOUNT> 0)ROLLBACK 
 select @output = upper(@TableAction)+ 
 'エラー-' + 
ケース(@TableAction)
の実行中にエラーが発生しました。 .____。] else @TableAction + 'ing '
 end + 
' records '+ 
 case(@TableAction)
 when' select 'then' from '
 when' update 'then 'in' 
 when 'insert' then 'into' 
 else 'from' 
 end + 
 '' + @TableName + 'table。' 
 select @output = @output + '@@ ERROR:' + convert(varchar(8)、@ error)
 select @output = @output + '@@ ROWCOUNT:' + convert(varchar( 8)、@ rowcount)
 
 select @output = @output + isnull(@AdditionalInfo、 '')
 exec LogEvent @Process、@Database、@Output 
 RAISERROR(@ output、16,1)with log 
 select @ReturnCode = -1 
 GOTO THE_EXIT 
 
 
3
datagod