sql >> データベース >  >> RDS >> Sqlserver

SQLServerでのエラーおよびトランザクション処理の実装

    はじめに

    アプリケーションの設計と開発をどれほど懸命に試みても、エラーは常に発生します。 2つの一般的なカテゴリがあります。構文エラーまたは論理エラーは、プログラムエラーまたはデータベース設計の誤りの結果である可能性があります。そうしないと、ユーザー入力が間違っているためにエラーが発生する可能性があります。

    T-SQL(SQL Serverプログラミング言語)では、両方のエラータイプを処理できます。アプリケーションをデバッグして、将来のバグを回避するために何をする必要があるかを判断できます。

    ほとんどのアプリケーションでは、エラーをログに記録し、ユーザーフレンドリーなエラー報告を実装し、可能な場合はエラーを処理してアプリケーションの実行を継続する必要があります。

    ユーザーはステートメントレベルでエラーを処理します。これは、SQLコマンドのバッチを実行し、最後のステートメントで問題が発生した場合、その問題の前にあるすべてのものが暗黙のトランザクションとしてデータベースにコミットされることを意味します。これはあなたが望むものではないかもしれません。

    リレーショナルデータベースは、バッチステートメントの実行用に最適化されています。したがって、ステートメントのバッチを1つのユニットとして実行し、1つのステートメントが失敗した場合は、すべてのステートメントを失敗させる必要があります。これは、トランザクションを使用して実行できます。この記事では、エラー処理とトランザクションの両方に焦点を当てます。これらのトピックは強く関連しているためです。

    SQLエラー処理

    例外をシミュレートするには、繰り返し可能な方法で例外を生成する必要があります。最も単純な例であるゼロ除算から始めましょう:

    SELECT 1/0

    出力はスローされたエラーを説明します–発生したゼロ除算エラー 。しかし、このエラーは、ユーザーフレンドリーなメッセージを生成するために処理、ログ記録、またはカスタマイズされていませんでした。

    例外処理は、実行するステートメントをBEGINTRY…ENDTRYブロックに配置することから始まります。

    SQL Serverは、BEGINCATCH…ENDCATCHブロックでエラーを処理(キャッチ)します。このブロックでは、エラーのログ記録または処理用のカスタムロジックを入力できます。

    BEGIN CATCHステートメントは、ENDTRYステートメントの直後に続く必要があります。次に、最初のエラー発生時に実行がTRYブロックからCATCHブロックに渡されます。

    ここで、発生した例外に関するデータをログに記録するか、ユーザーフレンドリーなメッセージを作成するかなど、エラーの処理方法を決定できます。

    SQL Serverには、エラーの詳細を抽出するのに役立つ可能性のある組み込み関数があります。

    • ERROR_NUMBER():SQLエラーの数を返します。
    • ERROR_SEVERITY():発生した問題のタイプとそのレベルを示す重大度レベルを返します。レベル11から16は、ユーザーが処理できます。
    • ERROR_STATE():エラー状態番号を返し、スローされた例外に関する詳細を提供します。エラー番号を使用して、Microsoftナレッジベースで特定のエラーの詳細を検索します。
    • ERROR_PROCEDURE():エラーが発生したプロシージャまたはトリガーの名前を返します。プロシージャまたはトリガーでエラーが発生しなかった場合はNULLを返します。
    • ERROR_LINE():エラーが発生した行番号を返します。プロシージャまたはトリガーの行番号、またはバッチ内の行番号である可能性があります。
    • ERROR_MESSAGE():エラーメッセージのテキストを返します。

    次の例は、エラーの処理方法を示しています。最初の例には、ゼロ除算が含まれています。 エラー、2番目のステートメントは正しいです。

    BEGIN TRY
       PRINT 1/0  
       SELECT 'Correct text'
    END TRY
    BEGIN CATCH
       SELECT ERROR_NUMBER() AS ERR_NO
       ,      ERROR_SEVERITY() AS ERR_SEV
       ,      ERROR_STATE() AS ERR_STATE
       ,      ERROR_LINE() AS ERR_LINE
       ,      ERROR_MESSAGE() AS ERR_MESSAGE
    END CATCH
    

    2番目のステートメントがエラー処理なしで実行された場合(SELECT「正しいテキスト」)、成功します。

    TRY-CATCHブロックにカスタムエラー処理を実装しているため、プログラムの実行は最初のステートメントのエラーの後にCATCHブロックに渡され、2番目のステートメントは実行されませんでした。

    このようにして、ユーザーに提供されるテキストを変更し、エラーがより適切に発生した場合に何が起こるかを制御できます。たとえば、エラーをログテーブルに記録してさらに分析します。

    トランザクションの使用

    ビジネスロジックは、2番目のステートメントが失敗したときに最初のステートメントの挿入が失敗した、または2番目のステートメントが失敗したときに最初のステートメントの変更を繰り返す必要があると判断する場合があります。トランザクションを使用すると、ステートメントのバッチを、失敗または成功する1つの単位として実行できます。

    次の例は、トランザクションの使用法を示しています。

    まず、保存されたデータをテストするためのテーブルを作成します。次に、TRY-CATCHブロック内の2つのトランザクションを使用して、トランザクションの一部が失敗した場合に発生することをシミュレートします。

    XACT_STATE()ステートメントでCATCHステートメントを使用します。 XACT_STATE()関数は、トランザクションがまだ存在するかどうかを確認するために使用されます。トランザクションが自動的にロールバックする場合、ROLLBACKTRANSACTIONは新しい例外を生成します。

    以下のコードで戦利品を持っています:

    -- CREATE TABLE TEST_TRAN(VALS INT)
    
    BEGIN TRY
       BEGIN TRANSACTION
           INSERT INTO TEST_TRAN(VALS) VALUES(1);
       COMMIT TRANSACTION  
    
       BEGIN TRANSACTION
           INSERT INTO TEST_TRAN(VALS) VALUES(2);
           INSERT INTO TEST_TRAN(VALS) VALUES('A'); 
           INSERT INTO TEST_TRAN(VALS) VALUES(3);
       COMMIT TRANSACTION
    END TRY
    BEGIN CATCH  
       IF XACT_STATE() > 0 ROLLBACK TRANSACTION
    
       SELECT ERROR_NUMBER() AS ERR_NO
       ,      ERROR_SEVERITY() AS ERR_SEV
       ,      ERROR_STATE() AS ERR_STATE
       ,      ERROR_LINE() AS ERR_LINE
       ,      ERROR_MESSAGE() AS ERR_MESSAGE
    
    END CATCH
    
    SELECT * FROM TEST_TRAN
    
    -- DROP TABLE TEST_TRAN
    

    この画像は、TEST_TRANテーブルの値とエラーメッセージを示しています。

    ご覧のとおり、最初の値のみがコミットされました。 2番目のトランザクションでは、2行目に型変換エラーが発生しました。したがって、バッチ全体がロールバックされました。

    このようにして、データベースに入力するデータとバッチの処理方法を制御できます。

    SQLでのカスタムエラーメッセージの生成

    カスタムエラーメッセージを作成したい場合があります。通常、これらは、問題が発生する可能性があることがわかっているシナリオを対象としています。技術的な詳細を表示せずに、何か問題が発生したことを示す独自のカスタムメッセージを作成できます。そのために、THROWキーワードを使用しています。

    BEGIN TRY
       IF ( SELECT COUNT(sys.all_objects) > 1 )
    	THROW ‘More than one object is ALL_OBJECTS system table’
    END TRY
    BEGIN CATCH
       SELECT ERROR_NUMBER() AS ERR_NO
       ,      ERROR_SEVERITY() AS ERR_SEV
       ,      ERROR_STATE() AS ERR_STATE
       ,      ERROR_LINE() AS ERR_LINE
       ,      ERROR_MESSAGE() AS ERR_MESSAGE
    END CATCH
    

    または、エラーの監視と報告の分類と一貫性を保つために、カスタムエラーメッセージのカタログが必要です。 SQL Serverでは、エラーメッセージコード、重大度、および状態を事前に定義できます。

    「sys.sp_addmessage」と呼ばれるストアドプロシージャは、カスタムエラーメッセージを追加するために使用されます。これを使用して、複数の場所でエラーメッセージを呼び出すことができます。

    コード内の複数の場所に同じエラーの詳細をハードコーディングする代わりに、RAISERRORを呼び出して、メッセージ番号をパラメーターとして送信できます。

    以下から選択したコードを実行することにより、カスタムエラーをSQL Serverに追加して発生させ、 sys.sp_dropmessageを使用します。 指定されたユーザー定義のエラーメッセージをドロップするには:

    exec sys.sp_addmessage @msgnum=55000, @severity = 11, 
                                              @msgtext = 'My custom error message'
    GO
    
    RAISERROR(55000,11,1)
    GO
    
    exec sys.sp_dropmessage @msgnum=55000
    GO
    

    また、以下のクエリフォームを実行することで、SQLServerのすべてのメッセージを表示できます。カスタムエラーメッセージは、結果セットの最初のアイテムとして表示されます:

    SELECT * FROM master.dbo.sysmessages

    エラーをログに記録するシステムを作成する

    後でデバッグおよび処理するためにエラーをログに記録すると常に役立ちます。また、これらのログに記録されたテーブルにトリガーを設定したり、メールアカウントを設定したりして、エラーが発生したときにユーザーに通知する方法を少し工夫することもできます。

    エラーをログに記録するために、 DBError_Logというテーブルを作成します 、ログ詳細データの保存に使用できます:

    CREATE TABLE DBError_Log
    (
        DBError_Log_ID    INT IDENTITY(1, 1) PRIMARY KEY,
        UserName              VARCHAR(100),
        ErrorNumber    INT,
        ErrorState     INT,
        ErrorSeverity  INT,
        ErrorLine      INT,
        ErrorProcedure VARCHAR(MAX),
        ErrorMessage   VARCHAR(MAX),
        ErrorDateTime  DATETIME
    );
    

    ロギングメカニズムをシミュレートするために、 GenErrorを作成しています。 ゼロ除算を生成するストアドプロシージャ エラーが発生し、エラーが DBError_Logに記録されます テーブル:

    CREATE PROCEDURE dbo.GenError
    AS
      BEGIN TRY
        SELECT 1/0
      END TRY
      BEGIN CATCH
        INSERT INTO dbo.DBError_Log
        VALUES
        (SUSER_SNAME(),
         ERROR_NUMBER(),
         ERROR_STATE(),
         ERROR_SEVERITY(),
         ERROR_LINE(),
         ERROR_PROCEDURE(),
         ERROR_MESSAGE(),
         GETDATE()
    	);
      END CATCH
    GO
    
    EXEC dbo.GenError
    SELECT * FROM  dbo.DBError_Log
    

    DBError_Log 表には、エラーをデバッグするために必要なすべての情報が含まれています。また、エラーの原因となった手順に関する追加情報も提供します。これは簡単な例のように思えるかもしれませんが、このテーブルを追加のフィールドで拡張したり、カスタム作成された例外で埋めたりすることができます。

    結論

    アプリケーションを保守およびデバッグする場合は、少なくとも、問題が発生したことを報告し、内部でログに記録する必要があります。何百万ものユーザーが本番レベルのアプリケーションを使用している場合、実行時に問題をデバッグするには、一貫性のある報告可能なエラー処理が重要です。

    元のエラーをデータベースエラーログに記録することはできますが、ユーザーにはよりわかりやすいメッセージが表示されるはずです。したがって、呼び出し元のアプリケーションにスローされるカスタムエラーメッセージを実装することをお勧めします。

    実装する設計が何であれ、ユーザーとシステムの例外をログに記録して処理する必要があります。このタスクはSQLServerでは難しくありませんが、最初から計画する必要があります。

    すでに実稼働環境で実行されているデータベースにエラー処理操作を追加すると、深刻なコードのリファクタリングと見つけにくいパフォーマンスの問題が発生する可能性があります。


    1. フェイルオーバー前またはフェイルオーバー後のスクリプトを使用したMySQLおよびMariaDBのレプリケーションフェイルオーバーの制御

    2. INSERTINTOとSELECTINTO

    3. MariaDBでのPERIOD_ADD()のしくみ

    4. 絶対に避けるべき5つの非常に一般的なSQLクエリの設計ミス