ストアドプロシージャを十分に堅牢にし、非常に適切にスケーリングし、エラー処理を含めるための良い方法は何ですか?
さらに、ストアドプロシージャで複数のエラーシナリオを処理し、呼び出し元のアプリに意味のあるエラー情報を返すインテリジェントなフィードバックシステムを使用するための最良の方法は何ですか?
ストアドプロシージャを十分に堅牢にし、非常に適切にスケーリングし、エラー処理を含めるための良い方法は何ですか?
さらに、ストアドプロシージャで複数のエラーシナリオを処理し、呼び出し元のアプリに意味のあるエラー情報を返すインテリジェントなフィードバックシステムを使用するための最良の方法は何ですか?
回答:
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があります。これにより、特にロギングの目的で、エラーメッセージの作成に柔軟性が追加されます。
これは私たちのテンプレートです(エラーログは削除されました)
ノート:
...したがって、必要以上の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
私は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に移動