【PL/SQL】AUTONOMOUS_TRANSACTIONで独立処理を実装する完全ガイド|5ユースケース・デッドロック対策・ORA-06519・SAVEPOINT比較

【PL/SQL】AUTONOMOUS TRANSACTIONで独立した処理を行う方法 PL/SQL

PL/SQLで「親トランザクションがROLLBACKされても絶対に残したい記録」を実装するには、PRAGMA AUTONOMOUS_TRANSACTIONが唯一の正解です。監査ログ・エラー記録・リトライカウンタ・レート制限など、「親が失敗しても確定させる必要がある」処理を呼び出し元と独立した別トランザクションで動かせる仕組みです。

ただし「便利だから何でも自律トランザクションにする」という濫用は本番障害の温床です。AUTONOMOUS_TRANSACTIONは親と完全に独立した別セッションのように振る舞うため、親と同じ表を更新するとデッドロックCOMMIT忘れでORA-06519業務データに使うと整合性破壊といった重大事故を起こします。

この記事ではAUTONOMOUS_TRANSACTIONの正しい実装と運用を実戦コード中心で整理します。5つの具体ユースケース(監査ログ/エラー記録/リトライカウンタ/レート制限/キュー投入)の即実装可能コード、デッドロック設計、ORA-06519対策、SAVEPOINTとの使い分け、テスト方法、アンチパターン6選、FAQまで2026年版で解説します。

この記事でわかること

  • AUTONOMOUS_TRANSACTIONの正しい使い所と典型ユースケース5種
  • 監査ログAPIをパッケージ化した再利用可能な実装テンプレート
  • 親が失敗してもエラー記録だけ残すエラーログパターン
  • 親と同じ表を扱うときのデッドロック回避設計
  • ORA-06519(COMMIT/ROLLBACK忘れ)の発生条件と対処
  • SAVEPOINTとAUTONOMOUS_TRANSACTIONの決定的違いと使い分け
  • JDBCトランザクションマネージャとの相互作用
  • 独立トランザクションを使うときのテスト戦略
  • 本番で踏むアンチパターン6選
スポンサーリンク

30秒でわかるAUTONOMOUS_TRANSACTIONの結論

忙しい読者向けの結論先出しです。

結論 理由・効果
① 用途は監査ログ・エラー記録・カウンタ系に限定 業務データに使うと整合性破壊と運用ブラックボックス化
PRAGMA AUTONOMOUS_TRANSACTION必ずCOMMITまたはROLLBACKで終わらせる 忘れるとORA-06519が発生して呼び出しが失敗する
親と同じ表を更新しない 親が保持中のロックと衝突しデッドロックになる
④ 親のSELECT結果は子からは見えない 独立トランザクションなのでREAD COMMITTED分離となる
SAVEPOINTと混同しない SAVEPOINTは同一TX内のしおり、自律TXは別TX
⑥ ログAPIはパッケージ化して集中管理 あちこちで個別実装するとPRAGMA忘れの事故源になる
⑦ JDBCのトランザクション境界とは独立して動く クライアント側のrollbackでも自律TXのコミットは生き残る

動作の仕組み|呼び出し元との独立性

AUTONOMOUS_TRANSACTIONを宣言すると、そのプロシージャは呼び出されたとき親のトランザクションを一時停止し、新しい独立したトランザクションを開始します。内部でCOMMITまたはROLLBACKで終了したら親のトランザクションが再開する流れです。

独立しているもの/共有しているもの

「独立」という言葉から「完全に別セッション」と想像しがちですが、実際にはセッション固有のものは共有されています。正しい挙動を把握しましょう。

  • 独立: トランザクション(COMMIT/ROLLBACK)・ロックUNDOSAVEPOINT
  • 共有: セッションそのもの・パッケージ変数SYS_CONTEXTTEMPORARY TABLE(ON COMMIT PRESERVE ROWSの場合)・カーソル状態(呼出し元のは見える)
  • 分離: 親が更新中の未コミットデータは子からは見えない(READ COMMITTED分離)
独立性を実例で確認
-- 親トランザクションが更新中のデータは子から見えない
CREATE OR REPLACE PROCEDURE child_proc IS
  PRAGMA AUTONOMOUS_TRANSACTION;
  v_balance NUMBER;
BEGIN
  -- 親がUPDATEしたが未COMMITの値は見えない
  SELECT balance INTO v_balance FROM accounts WHERE id = 1;
  DBMS_OUTPUT.PUT_LINE('child sees: ' || v_balance);  -- 親のUPDATE前の値
  COMMIT;
END;
/

DECLARE
  v_b NUMBER;
BEGIN
  -- 親が更新する(未COMMIT)
  UPDATE accounts SET balance = 9999 WHERE id = 1;
  SELECT balance INTO v_b FROM accounts WHERE id = 1;
  DBMS_OUTPUT.PUT_LINE('parent sees: ' || v_b);  -- 9999(自分のセッション内)

  -- 子を呼ぶ
  child_proc;   -- ← childは元の値を見る(READ COMMITTED)

  ROLLBACK;     -- 親はROLLBACK
END;
/

「親が更新中の値が子から見えない」のは独立トランザクションの本質的な性質です。これにより親と子で同じ行を読むタイミングで結果が違う事象が発生します。監査ログのように「自身のINSERTだけ独立コミットしたい」用途では問題ないですが、業務データを子で読み取って判断する設計では予期しない結果になるので注意してください。

典型ユースケース5種|即実装可能コード集

実務でAUTONOMOUS_TRANSACTIONが活きる5つのシナリオを即実装可能なコード付きで紹介します。いずれも「親が失敗しても必ず残したい・独立して動かしたい」処理です。

ユースケース1|監査ログ(共通ライブラリAPI)

すべての業務処理から呼び出される共通ログ書き込みパッケージ。個別箇所でPRAGMA宣言を書くのではなく、1本のパッケージに集約して再利用するのがベストプラクティスです。

ユースケース1:共通監査ログAPI
-- ログ表
CREATE TABLE audit_log(
  log_id    NUMBER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  log_ts    TIMESTAMP DEFAULT SYSTIMESTAMP,
  who       VARCHAR2(64) DEFAULT USER,
  module    VARCHAR2(64),
  level_cd  VARCHAR2(10),  -- INFO/WARN/ERROR
  message   VARCHAR2(4000),
  context   CLOB
);

-- 監査ログAPIパッケージ
CREATE OR REPLACE PACKAGE pkg_audit AS
  PROCEDURE info (p_module VARCHAR2, p_msg VARCHAR2, p_ctx CLOB DEFAULT NULL);
  PROCEDURE warn (p_module VARCHAR2, p_msg VARCHAR2, p_ctx CLOB DEFAULT NULL);
  PROCEDURE error(p_module VARCHAR2, p_msg VARCHAR2, p_ctx CLOB DEFAULT NULL);
END pkg_audit;
/

CREATE OR REPLACE PACKAGE BODY pkg_audit AS

  -- 内部の汎用書き込み(独立トランザクション)
  PROCEDURE write_log(
    p_module VARCHAR2, p_level VARCHAR2,
    p_msg VARCHAR2, p_ctx CLOB
  ) IS
    PRAGMA AUTONOMOUS_TRANSACTION;
  BEGIN
    INSERT INTO audit_log(module, level_cd, message, context)
    VALUES(p_module, p_level, SUBSTR(p_msg, 1, 4000), p_ctx);
    COMMIT;   -- 親に影響せず確定
  EXCEPTION
    WHEN OTHERS THEN
      ROLLBACK;   -- ログ書き込み失敗は親に伝えず黙殺
  END;

  PROCEDURE info(p_module VARCHAR2, p_msg VARCHAR2, p_ctx CLOB) IS
  BEGIN write_log(p_module, 'INFO',  p_msg, p_ctx); END;

  PROCEDURE warn(p_module VARCHAR2, p_msg VARCHAR2, p_ctx CLOB) IS
  BEGIN write_log(p_module, 'WARN',  p_msg, p_ctx); END;

  PROCEDURE error(p_module VARCHAR2, p_msg VARCHAR2, p_ctx CLOB) IS
  BEGIN write_log(p_module, 'ERROR', p_msg, p_ctx); END;
END pkg_audit;
/

-- 利用例
BEGIN
  -- 業務処理(途中で失敗)
  UPDATE accounts SET balance = balance - 100 WHERE id = 1;
  pkg_audit.info('transfer', 'started');
  -- 失敗発生
  RAISE_APPLICATION_ERROR(-20100, 'simulated');
EXCEPTION
  WHEN OTHERS THEN
    pkg_audit.error('transfer', SQLERRM,
      DBMS_UTILITY.FORMAT_ERROR_BACKTRACE);
    ROLLBACK;
    -- → 業務UPDATEは取消し、ログだけ残る
END;
/

ユースケース2|トリガーから監査記録を残す

テーブル更新時のトリガーで監査記録を残す古典的パターン。トリガー内のINSERTを独立化することで、親トランザクションがROLLBACKされても監査が残る設計になります(用途による・後述の注意点も確認)。

ユースケース2:監査トリガー
CREATE TABLE emp_audit(
  audit_id NUMBER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  emp_id   NUMBER, old_sal NUMBER, new_sal NUMBER,
  who      VARCHAR2(64), at_ts TIMESTAMP
);

CREATE OR REPLACE TRIGGER trg_emp_salary_audit
AFTER UPDATE OF salary ON employees
FOR EACH ROW
DECLARE
  PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN
  INSERT INTO emp_audit(emp_id, old_sal, new_sal, who, at_ts)
  VALUES(:OLD.id, :OLD.salary, :NEW.salary, USER, SYSTIMESTAMP);
  COMMIT;
END;
/

-- 注意: 親のUPDATE EMPLOYEESがROLLBACKされても
-- emp_auditには監査記録が残る(用途として正しいか業務要件を確認)

ユースケース3|リトライカウンタ/処理回数記録

失敗時のリトライカウンタを「失敗時にも必ず加算したい」要件。通常のUPDATEでは親のROLLBACKで一緒に戻ってしまうので、独立トランザクションで加算します。

ユースケース3:リトライカウンタの確実な加算
CREATE TABLE retry_counter(
  job_id    VARCHAR2(64) PRIMARY KEY,
  retry_cnt NUMBER DEFAULT 0 NOT NULL,
  last_at   TIMESTAMP
);

CREATE OR REPLACE PROCEDURE pkg_retry.bump(p_job_id VARCHAR2) IS
  PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN
  -- UPSERTで増分
  MERGE INTO retry_counter t
  USING (SELECT p_job_id AS job_id FROM dual) s
     ON (t.job_id = s.job_id)
  WHEN MATCHED THEN UPDATE SET
    t.retry_cnt = t.retry_cnt + 1,
    t.last_at   = SYSTIMESTAMP
  WHEN NOT MATCHED THEN INSERT(job_id, retry_cnt, last_at)
    VALUES(p_job_id, 1, SYSTIMESTAMP);
  COMMIT;
END;
/

-- 利用例:処理が失敗してもカウンタは確実に増える
BEGIN
  pkg_retry.bump('JOB_ABC');
  -- 業務処理
  pkg_xxx.do();
  COMMIT;
EXCEPTION
  WHEN OTHERS THEN
    ROLLBACK;   -- 業務はROLLBACK
    RAISE;      -- だがカウンタはCOMMIT済み
END;
/

ユースケース4|レート制限(API呼び出し回数の追跡)

外部APIの呼び出し回数を集計して、一定時間内のリクエスト数を制限する用途。業務処理が成功しても失敗しても呼び出した事実だけは記録したいので独立トランザクションが向きます。

ユースケース4:API呼び出し回数の追跡
CREATE TABLE api_rate_limit(
  api_key   VARCHAR2(64),
  bucket_ts TIMESTAMP,
  call_cnt  NUMBER DEFAULT 0,
  PRIMARY KEY(api_key, bucket_ts)
);

CREATE OR REPLACE FUNCTION pkg_rate.check_and_record(
  p_api_key VARCHAR2,
  p_limit   NUMBER DEFAULT 100   -- 1分間に100回まで
) RETURN BOOLEAN AS
  PRAGMA AUTONOMOUS_TRANSACTION;
  v_bucket TIMESTAMP := TRUNC(SYSTIMESTAMP, 'MI');
  v_cnt    NUMBER;
BEGIN
  -- 現在のバケットの件数を取得して+1
  MERGE INTO api_rate_limit t
  USING (SELECT p_api_key k, v_bucket b FROM dual) s
     ON (t.api_key = s.k AND t.bucket_ts = s.b)
  WHEN MATCHED THEN UPDATE SET t.call_cnt = t.call_cnt + 1
  WHEN NOT MATCHED THEN INSERT(api_key, bucket_ts, call_cnt)
    VALUES(s.k, s.b, 1);

  SELECT call_cnt INTO v_cnt
  FROM api_rate_limit
  WHERE api_key = p_api_key AND bucket_ts = v_bucket;

  COMMIT;
  RETURN v_cnt <= p_limit;
END;
/

ユースケース5|キュー投入(イベントを必ず記録)

「処理が成功したか失敗したかにかかわらず、イベント発生の事実だけは外部キューに残す」監視・観測性向上の用途。実装は監査ログとほぼ同じパターンですが、本体トランザクションのCOMMIT後に処理を遅延させたい場合はAUTONOMOUS_TRANSACTIONよりDBMS_AQのような本物のキュー機能を使う方が適切な場合もあります。

ユースケース5:イベント記録テーブルへの即時投入
CREATE TABLE event_queue(
  event_id NUMBER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  occured_at TIMESTAMP DEFAULT SYSTIMESTAMP,
  event_type VARCHAR2(64),
  payload    CLOB,
  status     VARCHAR2(10) DEFAULT 'PENDING'
);

CREATE OR REPLACE PROCEDURE pkg_event.publish(
  p_type IN VARCHAR2, p_payload IN CLOB
) IS
  PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN
  INSERT INTO event_queue(event_type, payload)
  VALUES(p_type, p_payload);
  COMMIT;
END;
/

-- 利用例:処理結果に関わらず必ずイベントを記録
BEGIN
  pkg_event.publish('order.attempt',
    '{"order_id": 100, "user_id": 50}');
  -- 業務処理
  pkg_order.create_order(100, 50);
  COMMIT;
EXCEPTION
  WHEN OTHERS THEN
    pkg_event.publish('order.failed',
      '{"order_id": 100, "error": "' || SQLERRM || '"}');
    ROLLBACK;
    RAISE;
END;
/

デッドロック対策|親と同じ表に触れる場合の罠

AUTONOMOUS_TRANSACTIONは独立した別トランザクションのため、親が保持しているロックは子から見ると他セッションが持っているロックと同じ扱いになります。結果として、親と子が同じ行や表をUPDATEすると自分自身でデッドロックを起こします。

デッドロックを起こす実装と回避策
-- ❌ デッドロック発生例
CREATE OR REPLACE PROCEDURE bad_logger(p_id NUMBER) IS
  PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN
  -- 親が更新中の同じ表を子も更新→ロック待ち
  UPDATE orders SET status = 'LOG_RECORDED'
   WHERE order_id = p_id;
  COMMIT;
END;
/

DECLARE
BEGIN
  UPDATE orders SET status = 'PROCESSING' WHERE order_id = 100;
  bad_logger(100);   -- ← ここで永久ロック待ち(タイムアウトでORA-00060)
  COMMIT;
END;
/

-- ✅ 回避策1: 別の表に書く(推奨)
CREATE OR REPLACE PROCEDURE good_logger(p_id NUMBER, p_msg VARCHAR2) IS
  PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN
  -- 監査用の別テーブルに書く(親はordersをロック中だが
  -- audit_logは独立しているので衝突なし)
  INSERT INTO audit_log(module, message, context)
  VALUES('orders', p_msg, TO_CHAR(p_id));
  COMMIT;
END;
/

-- ✅ 回避策2: 同じ表を読むだけにする(書かない)
CREATE OR REPLACE PROCEDURE read_only_logger(p_id NUMBER) IS
  PRAGMA AUTONOMOUS_TRANSACTION;
  v_status VARCHAR2(20);
BEGIN
  -- 読み取り専用ならロック衝突なし(READ COMMITTED)
  -- ただし親の未COMMITの更新は見えない点に注意
  SELECT status INTO v_status FROM orders WHERE order_id = p_id;
  INSERT INTO audit_log(module, message)
  VALUES('orders', 'pre-status: ' || v_status);
  COMMIT;
END;
/

同一行への自己デッドロックは検出されない場合があるのが厄介です。通常のデッドロック(複数セッション間)はOracleが検出してORA-00060を返しますが、同一セッション内の親子間でロック待ちが発生するとそのままハングし、DDL_LOCK_TIMEOUT等のタイムアウトでようやくエラーになります。AUTONOMOUS_TRANSACTIONを書くときは「親は何のロックを持っているか」を常に意識し、同じ表への書き込みを避けてください。

ORA-06519対策|COMMIT/ROLLBACK忘れの典型エラー

ORA-06519: アクティブな自律トランザクションが検出されましたは、AUTONOMOUS_TRANSACTIONブロック内でCOMMITまたはROLLBACKを実行せずに終了した場合に発生します。基本的なミスですが、例外処理パスで漏れて見逃されることが多いエラーです。

ORA-06519の発生例と防止パターン
-- ❌ ORA-06519が発生する例
CREATE OR REPLACE PROCEDURE leaky_log(p_msg VARCHAR2) IS
  PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN
  INSERT INTO audit_log(message) VALUES(p_msg);
  -- COMMIT忘れ!
END;
/

BEGIN leaky_log('test'); END;
-- ORA-06519: アクティブな自律トランザクションが検出されました

-- ✅ 防止策1: 必ず終端で明示的COMMIT
CREATE OR REPLACE PROCEDURE safe_log(p_msg VARCHAR2) IS
  PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN
  INSERT INTO audit_log(message) VALUES(p_msg);
  COMMIT;
END;
/

-- ✅ 防止策2: 例外時もROLLBACKを必ず書く
CREATE OR REPLACE PROCEDURE robust_log(p_msg VARCHAR2) IS
  PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN
  INSERT INTO audit_log(message) VALUES(p_msg);
  COMMIT;
EXCEPTION
  WHEN OTHERS THEN
    ROLLBACK;     -- 例外時もきちんと閉じる
    -- ログAPI内の失敗は親に伝えない(黙殺)
END;
/

-- ✅ 防止策3: 標準テンプレートに沿って書く
CREATE OR REPLACE PROCEDURE template_log(p_msg VARCHAR2) IS
  PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN
  -- 業務処理
  INSERT INTO audit_log(message) VALUES(p_msg);

  COMMIT;            -- 必須:成功パス
EXCEPTION
  WHEN OTHERS THEN
    ROLLBACK;        -- 必須:失敗パス
    NULL;            -- ログ失敗は親に伝えない/RAISEするなら明示
END;
/

テンプレート徹底が最強の防止策です。①関数の最後に必ずCOMMIT、②EXCEPTION句で必ずROLLBACK、の2点をテンプレ化してコードレビューでチェック対象にしてください。社内のSnippetやIDEテンプレートに登録しておくと新規実装時の漏れがゼロになります。

SAVEPOINT との使い分け|決定木で判断する

「親トランザクションに影響を与えたくない」要件でSAVEPOINTとAUTONOMOUS_TRANSACTIONはよく混同されますが、決定的に役割が異なります。次の判断フローで明確に分けてください。

判断フローチャート

「親と独立した別トランザクションが必要か?」を最初に問います。

  • YES(親が失敗しても残したい記録)→ AUTONOMOUS_TRANSACTION
  • NO(親と同じトランザクション内で完結)→ 次の質問へ

NOの場合さらに:「途中状態を部分的に取り消したいか?」

  • YES(ステップ単位で巻き戻したい)→ SAVEPOINT
  • NO(成功か失敗かの2択でよい)→ 普通のCOMMIT/ROLLBACKのみ

具体例での判断

  • 「失敗時もエラーログだけは残したい」→ AUTONOMOUS_TRANSACTION
  • 「失敗行だけスキップして他の行は処理したい」→ SAVEPOINT
  • 「リトライ回数を必ず数えたい」→ AUTONOMOUS_TRANSACTION
  • 「ステップ2が失敗したらステップ1まで戻したい」→ SAVEPOINT
  • 「外部APIの呼出し回数を独立に集計したい」→ AUTONOMOUS_TRANSACTION
  • 「複数ジョブの失敗を吸収して成功した分だけ残したい」→ SAVEPOINT

SAVEPOINTとの併用は可能です。監査ログAPIをAUTONOMOUS_TRANSACTIONで実装し、業務処理側ではSAVEPOINTで段階的ロールバックする、という組み合わせが本格的なアプリケーションでは標準的。SAVEPOINTの詳細はSAVEPOINTで部分ロールバックを実装する完全ガイドを参照してください。

テスト方法|独立トランザクションを単体テストする

AUTONOMOUS_TRANSACTIONは独立コミットするため、テスト後のクリーンアップが厄介になります。通常の単体テストは「テスト前後で状態が変わらないようにROLLBACKで戻す」前提で書きますが、自律トランザクションはROLLBACKでは戻りません。対処パターンを整理します。

AUTONOMOUS_TRANSACTIONのテストパターン
-- パターン1: テスト前後で対象表をクリーンアップ
CREATE OR REPLACE PROCEDURE test_audit_log AS
  v_cnt NUMBER;
BEGIN
  -- 準備:監査表をクリア
  DELETE FROM audit_log WHERE module = 'TEST_MODULE';
  COMMIT;

  -- テスト実行
  pkg_audit.info('TEST_MODULE', 'test message');

  -- 検証
  SELECT COUNT(*) INTO v_cnt
  FROM audit_log
  WHERE module = 'TEST_MODULE' AND level_cd = 'INFO';

  IF v_cnt != 1 THEN
    RAISE_APPLICATION_ERROR(-20999, 'audit log not recorded');
  END IF;

  -- 後始末:テストデータ削除
  DELETE FROM audit_log WHERE module = 'TEST_MODULE';
  COMMIT;
END;
/

-- パターン2: 専用テストスキーマ/テスト用接尾辞で隔離
-- AUTONOMOUS関数を本物のログ表ではなく
-- _TEST 接尾辞のテーブルに向ける設定で実行
-- → 設定値はSYS_CONTEXTやパッケージ定数で切替

-- パターン3: utPLSQLのbefore/afterでクリーンアップ
-- @BeforeEach: テスト前にDELETE+COMMITで初期状態へ
-- @AfterEach:  テスト後にDELETE+COMMITで後始末

utPLSQLフレームワークを使う場合、--%beforeeach--%aftereachにクリーンアップ処理を書くのが定石です。CIで自動実行するときは必ず専用のテストスキーマで走らせ、本番データを汚染しないようにしてください。

本番で踏むアンチパターン6選

① 業務データの更新に使う

「親が失敗しても在庫を減らしたい」のような業務要件でAUTONOMOUS_TRANSACTIONを使うと、整合性が完全に壊れます。在庫だけ減って注文が消える状態になり修正不能。業務データは必ず親と同じトランザクション内で扱ってください。

② COMMIT/ROLLBACKを書き忘れてORA-06519

ありがちな単純ミスですが、例外パスでだけ漏れて本番に出てしまうケースが頻発します。EXCEPTION句で必ずROLLBACKまたはCOMMITを書くテンプレートを徹底してください。

③ 親と同じ表を更新してデッドロック

独立トランザクションだから別セッション扱いになる、と理解せず同じ表を更新してハング。「同じ表は触らない」を原則とし、必要なら別表または読み取り専用にしてください。

④ 監査ログAPIを各箇所で個別実装

毎回PRAGMA宣言して個別にINSERT+COMMITを書く実装が散在すると、COMMIT忘れ・PRAGMA忘れが各所で発生します。必ず1本のパッケージに集約して全員がそれを呼ぶ規約に。

⑤ 例外を握りつぶして失敗を隠す

監査ログ書き込み自体が失敗した場合、親に伝えるべきか・黙殺すべきかの判断は重要です。一般論として「ログ機構の失敗で業務が止まるのは本末転倒」なので黙殺+ROLLBACKが正解ですが、セキュリティ要件で監査が必須な場合は親に伝搬させる必要があります。要件に応じて使い分けてください。

⑥ ネスト呼び出しで意図せず多重コミット

AUTONOMOUS関数の中からさらにAUTONOMOUS関数を呼ぶことは可能ですが、それぞれが独立してCOMMITするため、「全部成功してから書く」ような原子性は得られません。ネストする設計はできるだけ避け、必要なら親の単一AUTONOMOUS関数でまとめて実装してください。

よくある質問

QAUTONOMOUS_TRANSACTIONは新しいセッションが作られる?
Aセッションは同じです。トランザクションだけが独立していて、パッケージ変数・SYS_CONTEXT・カーソル状態などのセッションスコープは共有されています。「別セッションのように振る舞うのはトランザクション層だけ」と理解してください。JDBCで見ると同じ接続上で動いているように見えます。
Q親と子で同じパッケージ変数の値が見える?
A見えます。パッケージ変数はセッションスコープで管理されているため、親が代入した値は子からそのまま参照できますし、子で変更した値は親に戻ったときも反映されています。これは強みでもあり罠でもあるので、「独立トランザクションだから変数も独立」と誤解しないようにしてください。
QなぜトリガーでAUTONOMOUS_TRANSACTIONが必要なケースがある?
A通常のトリガー内ではCOMMITROLLBACKが実行できません(ORA-06519が発生)。AUTONOMOUS_TRANSACTIONを宣言すると、そのトリガーを独立トランザクションとして扱えるためCOMMITが書けるようになります。主に監査トリガーで使われますが、「業務テーブルの更新がROLLBACKされても監査だけ残す」用途は業務要件として正しいかを慎重に検討してください。
Q親のSELECT結果は子から見える?
A親がCOMMIT済みのデータは見えますが、未COMMITの更新は見えません(READ COMMITTED分離レベル)。同一セッションでも自律トランザクションは別TXなので、親が更新中で未COMMITのデータは「他のトランザクションの未確定データ」扱いになるためです。これにより親と子で「同じ行を読んだのに値が違う」現象が起こり得ます。
Q関数の戻り値で結果を親に返せる?
Aはい、戻り値・OUTパラメータは通常通り使えます。AUTONOMOUS_TRANSACTIONはあくまでDBトランザクションの独立性であり、PL/SQLの関数呼び出し規約は通常通りです。この性質を活かしてリトライカウンタの現在値を返すなど、副作用と戻り値を組み合わせた実装もよく使われます。
Q何個までネストできる?
Aセッションあたり最大255個のアクティブな自律TXをネストできます(実際にこの数まで深くなるシステムは稀)。ただしネストしすぎると論理が追えなくなるので、実用的には1〜2階層に留めるべきです。ネスト過多は設計の見直しサインと捉えてください。
QJDBC側でrollback()するとAUTONOMOUS_TRANSACTIONはどうなる?
AJDBC側のrollbackは親のトランザクションだけ取り消します。自律TXで既にCOMMIT済みの内容は残ります。これがAUTONOMOUS_TRANSACTIONの本質的な価値で、「Java側でロールバックしても監査ログだけは確実に残せる」という保証をDB側で実現できます。
QAUTONOMOUS_TRANSACTIONはパフォーマンスに影響する?
A軽微なオーバーヘッドがあります。別トランザクションコンテキストの開始・終了処理が入るため、高頻度で呼び出すとミリ秒単位の遅延が積み重なる可能性があります。ログ書き込みのように1リクエストに1〜数回程度なら無視できる範囲。バッチで1秒に数千回呼ぶような使い方ではバッファに溜めてバルクINSERTする工夫を検討してください。
QPRAGMA AUTONOMOUS_TRANSACTIONを書く位置は?
ADECLARE節(変数宣言部)に書きます。プロシージャ・ファンクション・トリガー・パッケージ本体・匿名ブロックのいずれでも可能。パッケージ仕様(SPEC)には書けず、本体(BODY)のサブプログラム単位で宣言するのがルール。正確な位置を間違えるとコンパイルエラーになるので注意してください。
Q監査ログ用のサブプロシージャを呼ぶときPRAGMAを書く位置は?
A監査ログを実装するサブプロシージャ側にPRAGMAを書きます。呼び出し側には書きません。「監査ログAPIパッケージのサブプロシージャ全部にPRAGMAを書く」のではなく、「内部で実際にCOMMITする1つの関数」だけに書き、他のラッパー関数(INFO/WARN/ERRORなど)はそれを呼ぶだけにするとPRAGMA忘れの事故が減ります。本記事のユースケース1の実装を参考にしてください。

関連記事で深掘りする

AUTONOMOUS_TRANSACTIONに関連する周辺技術もあわせて押さえましょう。

まとめ|AUTONOMOUS_TRANSACTIONを正しく使い切る

AUTONOMOUS_TRANSACTIONは「親と独立した別トランザクション」を実現する強力な仕組みで、監査ログ・エラー記録・カウンタなど「親が失敗しても残したい記録」の実装に欠かせません。ただし業務データに使うと整合性が壊れ、同じ表に触れるとデッドロック、COMMIT忘れでORA-06519と事故源も多い機能です。本記事の要点を7つに集約します。

  1. 用途は監査ログ・エラー記録・カウンタ系に限定し、業務データには使わない
  2. 必ずCOMMITまたはROLLBACKで終わらせ、ORA-06519を防ぐ
  3. 親と同じ表を更新するとデッドロックする。原則は別表に書く
  4. 監査ログはパッケージ化して集中管理、PRAGMAは内部関数1本だけ
  5. SAVEPOINTとは別物。「親と独立か同一か」で機能を選ぶ
  6. テストは前後でクリーンアップが必要。utPLSQLのhookを活用
  7. JDBCのrollbackには影響されず、独立コミットの内容は確実に残る

レガシーコードで「失敗時にエラーログがROLLBACKされて記録が残らない」事故が起きていれば、共通ログAPIをAUTONOMOUS_TRANSACTIONで実装し直すだけで運用が劇的に改善します。本記事のユースケース5種を実装テンプレとして自プロジェクトに適用してみてください。