PRAGMA AUTONOMOUS_TRANSACTION は、メインのトランザクション内で独立した操作を実行し、その成功または失敗がメインのトランザクションに影響を与えないようにします。自律型トランザクションは、独自のコミットおよびロールバックロジックを持つ完全に独立したトランザクションを開始するため、監査ログやエラーログなどのシナリオに最適です。メインのトランザクションがロールバックされた場合でも、自律型トランザクションによって記録されたログは永続化され、すべての操作が追跡可能であることが保証されます。
仕組み
自律型トランザクションの核となる原則は隔離です。PRAGMA AUTONOMOUS_TRANSACTION を含むプログラムを呼び出すと、現在の親トランザクションは一時停止され、新しい独立したサブトランザクションが開始されます。
隔離: 自律型トランザクションは、親トランザクションからのコミットされていない変更を見ることはできず、その逆も同様です。
独立したコミットとロールバック: 自律型トランザクション内の
COMMITまたはROLLBACKは、そのトランザクションにのみ影響し、親トランザクションの最終的なCOMMITまたはROLLBACKからは独立しています。接続の消費: 自律型トランザクションを呼び出すたびに、個別のデータベース接続が作成され、トランザクションが終了すると解放されます。
注意事項
PRAGMA AUTONOMOUS_TRANSACTION ディレクティブは、次の SPL プログラムユニットの宣言セクションで使用できます。
スタンドアロンのストアドプロシージャと関数。
匿名ブロック。
パッケージで宣言されたストアドプロシージャと関数。
トリガー。
オブジェクトタイプのメソッド。
システムの安定性とトランザクションの一貫性を確保するため、Oracle 構文互換性バージョン 2.0 ではトリガー内の自律型トランザクションはサポートされていません。
PRAGMA AUTONOMOUS_TRANSACTIONディレクティブは、宣言セクションの先頭に配置する必要があります。-- ストアドプロシージャで自律型トランザクションを使用するための正しい構文 CREATE OR REPLACE PROCEDURE procedure_name IS PRAGMA AUTONOMOUS_TRANSACTION; BEGIN -- ビジネスロジック COMMIT; END; -- トリガーで自律型トランザクションを使用するための正しい構文、Oracle 互換性 1.0 でのみ使用 CREATE OR REPLACE TRIGGER trigger_name AFTER INSERT OR UPDATE OR DELETE ON table_name DECLARE PRAGMA AUTONOMOUS_TRANSACTION; BEGIN -- ビジネスロジック COMMIT; END;
使用例
自律型匿名ブロックを使用して監査ログを記録する
このシナリオでは、メインのトランザクションがコミットされるかロールバックされるかに関係なく、監査ログが保存されることが保証されます。
従業員データ用の
empテーブルと、監査ログ用のempauditlogテーブルを作成します。CREATE TABLE emp ( emp_id INT, emp_name VARCHAR(50), job VARCHAR(50) ); CREATE TABLE empauditlog ( audit_date DATE, audit_user VARCHAR2(20), audit_desc VARCHAR2(100) );トランザクションを開始し、データを挿入してから、トランザクションをロールバックします。
BEGIN; INSERT INTO emp (emp_id, emp_name, job) VALUES (101, 'John Doe', 'Engineer'); DECLARE PRAGMA AUTONOMOUS_TRANSACTION; BEGIN INSERT INTO empauditlog VALUES (SYSDATE, USER, 'Added employee(s)'); COMMIT; END; ROLLBACK;親トランザクションがロールバックされたため、
empテーブルへのデータ挿入は元に戻され、データは永続化されません。ただし、自律型トランザクションは監査ログをempauditlogテーブルに個別に書き込んでコミットしたため、親トランザクションのロールバックの影響を受けません。-- emp テーブルをクエリします。メインのトランザクションがロールバックされたため、空のはずです。 SELECT * FROM emp WHERE emp_id = 101; -- empauditlog テーブルをクエリします。自律型トランザクションがコミットされたことを確認します。これにより、1 つの監査レコードが返されるはずです。 SELECT * FROM empauditlog;
自律型ストアドプロシージャを使用して失敗した試行をログに記録する
このシナリオでは、在庫が十分な場合にシステムが注文を作成します。在庫が不十分な場合、システムは注文の作成をロールバックしますが、ユーザーの試行のログは記録します。
3 つのテーブルを作成します:
orders(注文データ用)、inventory、およびoperation_log(失敗ログを含む)。在庫を 5 に初期化します。10 個のアイテムの注文を行うと、在庫不足の例外がトリガーされ、失敗がログに記録されます。-- 1. 注文テーブル (主要なビジネスデータ) CREATE TABLE orders ( order_id NUMBER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, user_id NUMBER, product_id NUMBER, qty NUMBER, order_time TIMESTAMP DEFAULT SYSDATE ); -- 2. 在庫テーブル CREATE TABLE inventory ( product_id NUMBER PRIMARY KEY, stock NUMBER ); -- 3. 操作ログテーブル (自律型トランザクションの書き込み用) CREATE TABLE operation_log ( log_id NUMBER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, user_id NUMBER, product_id NUMBER, action VARCHAR2(50), -- 'ORDER_ATTEMPT' など status VARCHAR2(20), -- 'SUCCESS' / 'FAILED' reason VARCHAR2(200), -- 失敗の理由 log_time TIMESTAMP DEFAULT SYSDATE ); -- 4. データの初期化 INSERT INTO inventory (product_id, stock) VALUES (1001, 5); -- 製品 1001 の在庫は 5 個のみです自律型トランザクションを使用してストアドプロシージャを作成し、親トランザクションがロールバックされた場合でも
operation_logが永続化されるようにします。CREATE OR REPLACE PROCEDURE log_order_attempt( p_user_id IN NUMBER, p_product_id IN NUMBER, p_reason IN VARCHAR2 ) IS PRAGMA AUTONOMOUS_TRANSACTION; -- プロシージャ内の宣言セクション BEGIN INSERT INTO operation_log ( user_id, product_id, action, status, reason, log_time ) VALUES ( p_user_id,p_product_id,'ORDER_ATTEMPT','FAILED',p_reason,SYSDATE ); COMMIT; -- 独立したコミット、メインのトランザクションの影響を受けない END;ユーザーが注文を試みます。在庫不足のため失敗しますが、自律型トランザクションによりログは独立してコミットされます。
DECLARE v_user_id NUMBER := 123; v_product_id NUMBER := 1001; v_order_qty NUMBER := 10; v_stock NUMBER; BEGIN SELECT stock INTO v_stock FROM inventory WHERE product_id = v_product_id FOR UPDATE; IF v_stock < v_order_qty THEN log_order_attempt( p_user_id => v_user_id, p_product_id => v_product_id, p_reason => 'Insufficient stock: need ' || v_order_qty || ', available ' || v_stock ); RAISE_APPLICATION_ERROR(-20001, 'Insufficient stock, order creation failed'); END IF; INSERT INTO orders (user_id, product_id, qty) VALUES (v_user_id, v_product_id, v_order_qty); UPDATE inventory SET stock = stock - v_order_qty WHERE product_id = v_product_id; COMMIT; EXCEPTION WHEN OTHERS THEN ROLLBACK; RAISE; END;結果を確認します: 親トランザクションがロールバックされたため、
ordersテーブルは空で、inventoryは変更されていません。ただし、自律型トランザクションがコミットされたため、operation_logテーブルにはレコードが含まれています。SELECT * FROM orders; SELECT * FROM inventory; SELECT * FROM operation_log;
ベストプラクティス
接続枯渇のリスク
各々の自律型トランザクションは、個別のデータベース接続を消費します。高同時実行シナリオでは、自律型トランザクションを過度に使用すると、データベースの max_connections 制限をすぐに使い果たし、新しい接続リクエストが失敗してサービスの中断につながる可能性があります。
リスクシナリオ: ループ内または頻繁に呼び出される関数内で自律型トランザクションを使用する。
ネストのリスク: 自律型トランザクションはネストできます。ネストの各レベルで追加の接続が消費されます。たとえば、3 つのレベルのネストされた自律型トランザクションを持つセッションは、一時的に 4 つの接続を使用します: 1 つはメインセッション用、3 つは自律型トランザクション用です。
キャパシティプランニング: 自律型トランザクションを使用する前に、それらがデータベース接続に与える追加の需要を評価してください。必要に応じて
max_connectionsパラメーターを調整します。ストレステストを実行してください。
推奨プラクティス
明示的なコミットまたはロールバック: 自律型トランザクションブロックの最後に
COMMITまたはROLLBACKを使用して、その結果を明示的に定義します。短く保つ: 自律型トランザクションは短く、単一のログエントリの挿入など、高速に実行される操作のみを含む必要があります。複雑で時間のかかるクエリの実行は避けてください。
特定のシナリオでの使用: 監査、ロギング、統計の更新など、メインのトランザクションロジックから独立した補助的なタスクにのみ使用します。
互換性に関する注意事項
PolarDB for PostgreSQL (Oracle 互換) における自律型トランザクションの実装は、Oracle Database と比較して重要な動作の違いがあります。この違いは、データベースの移行中に潜在的なリスクをもたらす可能性があります。
機能
| Oracle の動作 | PolarDB for PostgreSQL (Oracle 互換) の動作 | リスクの説明 |
明示的なコミットまたはロールバックがない |
| 暗黙的なコミット。コードブロックが | 高リスク。この動作は、データが予期せずコミットされることにつながり、トランザクションの原子性の原則に違反します。異なる環境間で一貫したコードの動作を保証するために、常に |
よくある質問
"sorry, too many clients already" エラーのトラブルシューティング方法
このエラーは、アクティブなデータベース接続の数が max_connections 制限に達したことを示します。自律型トランザクションが原因であると疑われる場合は、次の手順に従ってください。
現在の接続数と設定されている
max_connections制限を確認します。-- 接続の総数を表示 SELECT count(*) FROM pg_stat_activity; -- 現在の最大接続数を表示 SHOW max_connections;長時間実行されている自律型トランザクションを特定します。次のクエリを使用して、5 分以上実行されている自律型トランザクションに関連するクエリを見つけます。
SELECT pid, query_start, now() - query_start AS duration, query FROM pg_stat_activity WHERE state = 'active' AND query ILIKE '%PRAGMA AUTONOMOUS_TRANSACTION%' AND now() - query_start > interval '5 minutes';解決策:
コードの最適化: 自律型トランザクションを呼び出すループや長時間実行される操作を確認し、最適化します。
接続数の増加: 使用状況が妥当であるにもかかわらず接続がまだ不十分な場合は、システム全体への影響を評価した上で
max_connectionsを増やすことを検討してください。タイムアウトの設定: セッションに
statement_timeoutを設定して、長時間実行されるクエリを自動的に終了させ、接続が無期限に開かれたままになるのを防ぎます。