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


11

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

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


2
SQL Server 2005で新しいTRY CATCHブロックを使用してみてください。sommarskog.se/error_handling_2005.html
Sankar Reddy

こんにちは@Kacalapy〜今後はそれぞれの質問に独自に質問することをお勧めします。これにより、一度に1つの質問に焦点を当てた特定の回答を得ることができます。この質問でそれを行うことをお勧めします。
jcolebrand

回答:


12

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

このサイトから無料で入手できます。私はその会社とはまったく関係がありませんが、その本のハードコピー版を所有しています。

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

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

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

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

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

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


素晴らしいアドバイスと非常に良い本!
マリアン

Red Gateは非常に役立つ無料の電子書籍をいくつか提供しており、これは確かに優れた電子書籍の1つです。素晴らしい提案。
Matt M

彼らのすべての本がこれを行うわけではありませんが、Kuznetsovの「Defensive ...」本の無料版には、トランザクションの分離レベルと同時実行性を維持する変更の開発に関する最後の2つの章は含まれていません。私のために。そこのコンテンツは購入する価値がありました。
Phil Helmer、2011

7

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

ノート:

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

@@ TRANCOUNTが0より大きい場合はどうなりますか?あなたは仕事をしないか、フィードバックを持っていますか?
kacalapy 2011

@kacalapy:ネストされたトランザクションなどはないため、別のscribd.com/doc/49579859/33/Nested-Transactions-Are-Real
gbn

3

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

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

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

exec LogEvent @ Process、@ Database、「何とか何とか何とか挿入しようとしています」
試してみる
  MyTableに挿入
  値を選択
    MyOtherTableから

  @rowcount = @@ ROWCOUNTを選択します
トライを終了
-  エラー処理
キャッチを開始
  @error = ERROR_NUMBER()を選択し、
         @rowcount = -1
         @TableAction = '挿入'、
         @TableName = @Database + '.MyTable'、
         @AdditionalInfo = '(何とか何とか何とか挿入しようとしています)' + dbo.GetErrorInfo()
   GOTO TableAccessError
キャッチを終了

。
。
。
。

TableAccessError:
IF(@@ TRANCOUNT> 0)ロールバック
@output = upper(@TableAction)+を選択します 
       'エラー-エラーが発生しました' + 
       ケース(@TableAction)
         「更新」してから「更新」するとき
         「削除」してから「削除」するとき
         他の@TableAction + 'ing'
       終了+ 
       'レコード' + 
       ケース(@TableAction) 
         「選択」し、「から」の場合 
         「更新」してから「中に」 
         「挿入」してから「中に」
         それ以外は「から」   
         終了+ 
         '' + @TableName + 'テーブル。
select @output = @output + '@@ ERROR:' + convert(varchar(8)、@ error) 
@output = @output + '@@ ROWCOUNT:' + convert(varchar(8)、@ rowcount)を選択します 

select @output = @output + isnull(@AdditionalInfo、 '')
exec LogEvent @ Process、@ Database、@ Output
ログ付きのRAISERROR(@ output、16,1)
@ReturnCode = -1を選択します
THE_EXITに移動


弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.