問題の歴史
ビジネスロジックの処理では、しばしば関連する複数のテーブルのデータを変更する必要があります。何かがうまくいかなかった場合(例えば、制約が violatedした場合)、最後の操作だけでなく、ロジックに基づくすべてのアクションを取り消すことが重要です。これを実現するために、トランザクションメカニズムとエラー処理が使用されます。
問題
すべてのDBMSが、特にTRY/CATCHやEXCEPTIONハンドラが使用される場合に、エラーが発生した場合に複数ステップの手続きでロールバックを保証するわけではありません。トランザクションロジックを誤って書くと、「部分的な」変更(データの一部が更新され、一部は更新されない)を引き起こし、ビジネスの整合性を損ないます。
解決策
適切な処理には、エラーをキャッチするための明示的なトランザクションの開始と終了(BEGIN TRANSACTION, COMMIT/ROLLBACK)が必要です。異なるDBMSでは、構文と機能が異なります。
MS SQL Serverの例:
CREATE PROCEDURE update_balances(@from INT, @to INT, @amount DECIMAL(10,2)) AS BEGIN BEGIN TRY BEGIN TRANSACTION; UPDATE accounts SET balance = balance - @amount WHERE id = @from; UPDATE accounts SET balance = balance + @amount WHERE id = @to; COMMIT TRANSACTION; END TRY BEGIN CATCH ROLLBACK TRANSACTION; INSERT INTO error_log(err_message, err_date) VALUES(ERROR_MESSAGE(), GETDATE()); THROW; -- エラーをさらにthrowします END CATCH END;
PostgreSQLの例:
CREATE OR REPLACE FUNCTION update_balances(from_id INT, to_id INT, amount NUMERIC) RETURNS void AS $$ BEGIN UPDATE accounts SET balance = balance - amount WHERE id = from_id; UPDATE accounts SET balance = balance + amount WHERE id = to_id; -- 例外が発生すると、トランザクションは自動的にロールバックされます EXCEPTION WHEN OTHERS THEN INSERT INTO error_log(err_message, err_date) VALUES(SQLERRM, NOW()); RAISE; END; $$ LANGUAGE plpgsql;
主な特徴:
ストアドプロシージャ内のエラーが常にトランザクションを自動的にロールバックできますか? いいえ!例えば、MS SQL Serverではそのロールバックは保証されていません - 明示的なROLLBACKが必要です。PostgreSQLでは、エラーが発生するとトランザクション全体が「破損」し、終了を要求します。
一つのプロシージャ内で「部分的に」コミットできますか? 悪い実践です。ビジネス操作の最後でのみcommit/rollbackを実行することをお勧めします。一部のプラットフォームでは「savepoints」が可能ですが、これは特別なタスクのためです。
SQLでTRY/CATCH/EXCEPTIONを介して捕らえることができないエラーのタイプは何ですか? 一部のシステム障害(例えば、サーバーとの接続が切断されるなど)はハンドラにキャッチされず、ロールバックが不可能になります。
ストアドプロシージャは3つのテーブルを更新し、エラーキャッチなしでCOMMITで終了しました。そのため、2番目のテーブルでエラーが発生した場合、最初のテーブルはすでに変更されており、手動でバックアップから「ロールバック」するのに数時間かかりました。
利点:
単純で「動作します」、障害が発生するまで。
欠点:
不整合のリスク;エラー時に迅速に回復できない。
エラー処理を明示的に実装し、すべての障害をログに記録するトランザクションのロールバックを導入しました。データベースの整合性を保つための復元には数秒しかかからず、障害が分析され、最小化されます。
利点:
障害が発生しても、クリーンなデータの不変性が保証されている;明確なロジック。
欠点:
初心者には少し難しく、コードの規律が必要です。