【PL/SQL】SAVEPOINTで部分ロールバックを実装する完全ガイド|内部動作・4業務パターン・Saga・JDBC連携・AUTONOMOUS比較

【PL/SQL】SAVEPOINTを使った部分ロールバックの実装方法 PL/SQL

PL/SQLでバッチ処理やAPI実装を書いていると「失敗行だけ取り消して、他の処理は続けたい」という要件に必ず出会います。たとえば1万件のCSVを取り込む処理で1件だけ重複エラーになったとき、ROLLBACKで全件取り消したら他の9999件の処理が無駄になります。これを解決する仕組みがSAVEPOINTROLLBACK TO SAVEPOINTによる部分ロールバックです。

ただしSAVEPOINTは誤解されがちな機能でもあります。「ネストトランザクションのようなもの」と説明されることが多いですが、Oracleのトランザクションはネストしません。SAVEPOINTは同じトランザクション内で「戻り先のしおり」を打つ仕組みであり、AUTONOMOUS_TRANSACTIONとは根本的に異なります。この違いを理解せず使うと「ROLLBACK TOしたのにロックが解放されない」「カーソルがクリアされて続行できない」といった事故になります。

この記事ではSAVEPOINTの内部動作・実戦パターン・制限事項を徹底解説します。基本構文から始めて、ROLLBACK TOの正確な挙動、業務シナリオ別の高度パターン(行単位エラー隔離・Saga・分割API)、JDBC連携、SAVEPOINT vs AUTONOMOUS_TRANSACTION の使い分け、カーソル・ロックの保持挙動、アンチパターン、FAQまで2026年版で整理します。

この記事でわかること

  • SAVEPOINTの内部動作(UNDOセグメントとSCNの仕組み)
  • ROLLBACK TO実行時に「何が戻り、何が残るか」の正確な理解
  • 同名SAVEPOINTの上書きと階層管理パターン
  • 失敗行だけ隔離するループ処理の標準実装
  • 例外処理(EXCEPTION句)との統合テンプレート
  • Sagaパターンでの分散トランザクション風の実装
  • JDBC/JavaのConnection.setSavepoint()との連携
  • SAVEPOINT vs AUTONOMOUS_TRANSACTION の決定的違いと使い分け
  • SAVEPOINT後にカーソル・ロック・FORALLがどうなるかの制限事項
  • 本番で踏むアンチパターン6選
スポンサーリンク

30秒でわかるSAVEPOINTの結論

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

結論 理由・効果
① SAVEPOINTは同一トランザクション内のしおり ネストトランザクションではない。COMMIT前の戻り先機能
ROLLBACK TO sp1sp1以降のDMLだけ取消 sp1以前は残る。トランザクション全体は継続中
③ ループの行単位エラー隔離は各反復先頭でSAVEPOINT 失敗した1行だけ取消し、ログを記録して次へ
④ 同名SAVEPOINTは上書きされる 戻り先は最後のSAVEPOINTの位置になる
DDL/TRUNCATEで全SAVEPOINTが消える 暗黙コミットでトランザクション境界がリセット
⑥ ROLLBACK TO 後はカーソルがクリアされる FOR LOOP中のカーソルがROLLBACKで失効する罠
⑦ 別トランザクションが必要ならAUTONOMOUS_TRANSACTION SAVEPOINTは「同一トランザクション内」、独立性が必要なら別機能

SAVEPOINTの内部動作|UNDO・SCN・ロックの仕組み

SAVEPOINTを「ROLLBACK の戻り先」と表面的に理解するだけでは実装で罠を踏みます。Oracleの内部でSAVEPOINTが何をしているかを把握しましょう。

SAVEPOINT実行時に何が起きるか

SAVEPOINTを宣言すると、Oracleは現在のSCN(System Change Number、トランザクションの順序を表す内部時計)とUNDOセグメントの位置情報をマーカーとして保持します。実体としてはセッション内のメタデータ更新だけで、テーブルへの書き込みもCOMMITも一切発生しません。極めて軽量な操作なので、ループ内で毎反復SAVEPOINTを打ってもパフォーマンスへの影響はほぼ皆無です。

ROLLBACK TO実行時に何が起きるか

SAVEPOINT以降に発生したDMLのUNDO情報を逆順に適用してその時点のデータ状態を復元します。トランザクション自体は終わっていないので、取得済みのロックや内部状態は基本的に維持されます。ただしROLLBACK TO 以降に取得した行ロックは解放されます。一方、SAVEPOINT前から保持しているロックは残り続けます。

SAVEPOINTで保持されないもの

「すべてが綺麗に戻る」と思うと事故ります。SAVEPOINTで戻らない/消えるものを把握してください。①カーソルの位置(FOR LOOP内のカーソルはROLLBACKでクリアされる)、②パッケージ変数(PL/SQL変数は戻らない)、③シーケンスNEXTVAL(採番済み番号は戻らない)、④DDLの結果(暗黙コミットで全SAVEPOINTが消える)。これらは別途自分で管理する必要があります。

SCNの観点で見るSAVEPOINT:SCNはトランザクション内のすべてのDMLに番号を振っており、SAVEPOINTはその番号の特定の値をマーカーとして覚えるだけです。ROLLBACK TOは「このSCN以降のDMLをUNDOで戻す」という処理。この理解があると「なぜシーケンス番号は戻らないのか」(シーケンスはトランザクション外で動くから)のような疑問が論理的に解けます。

ROLLBACK TO の正確な挙動|何が戻り、何が残るか

「ROLLBACK TO で部分的に戻る」と説明されますが、具体的に何が戻り何が残るかを正確に把握していないと本番で予想外の挙動に遭遇します。実例で確認しましょう。

ROLLBACK TO の挙動を実例で確認
-- 準備
CREATE TABLE t(id NUMBER, val VARCHAR2(20));
CREATE SEQUENCE seq_t START WITH 100 INCREMENT BY 1;

DECLARE
  v_seq1 NUMBER;
  v_seq2 NUMBER;
  v_seq3 NUMBER;
BEGIN
  -- 開始:何もない状態
  INSERT INTO t VALUES (1, 'A');
  v_seq1 := seq_t.NEXTVAL;     -- 100が採番される
  DBMS_OUTPUT.PUT_LINE('seq1=' || v_seq1);

  SAVEPOINT sp1;               -- ★しおり①

  INSERT INTO t VALUES (2, 'B');
  v_seq2 := seq_t.NEXTVAL;     -- 101が採番される
  DBMS_OUTPUT.PUT_LINE('seq2=' || v_seq2);

  SAVEPOINT sp2;               -- ★しおり②

  INSERT INTO t VALUES (3, 'C');
  v_seq3 := seq_t.NEXTVAL;     -- 102が採番される
  DBMS_OUTPUT.PUT_LINE('seq3=' || v_seq3);

  -- sp1まで戻す
  ROLLBACK TO SAVEPOINT sp1;

  -- 何が残っている?
  --   テーブル:(1, 'A') だけ。(2,'B') と (3,'C') は戻る
  --   PL/SQL変数:v_seq1=100, v_seq2=101, v_seq3=102 全て残る
  --   シーケンス:次は103から(採番済みの100,101,102は消費済み)
  --   sp2:消える(sp1より新しいSAVEPOINTは無効化)
  --   sp1:残る(同じSAVEPOINTには何度でも戻れる)

  -- 続行可能:トランザクションは継続中
  INSERT INTO t VALUES (4, 'D');
  COMMIT;
END;
/

ROLLBACK TO で「戻るもの/残るもの」一覧

対象 挙動 備考
テーブルのDML(INSERT/UPDATE/DELETE) 戻る UNDO情報で復元
PL/SQL変数の値 残る PL/SQLメモリは戻らない。手動で再代入が必要
シーケンスNEXTVAL 残る(消費済) 採番した番号は戻らない
SAVEPOINT以降に取得した行ロック 解放 ROLLBACK対象のロックは消える
SAVEPOINT前から保持の行ロック 保持 トランザクションは継続中
ループ中のカーソル クリア FOR LOOP途中のROLLBACK TOで挙動不定
SAVEPOINTより新しいSAVEPOINT 消失 sp2を打った後ROLLBACK TO sp1するとsp2は無効化
SAVEPOINTそれ自体 残る 同じ点に何度でもROLLBACK可能
パッケージステート 残る PL/SQL変数と同様にDBMSの管理外

カーソルクリアの罠FOR rec IN cur LOOP ... ROLLBACK TO ... END LOOP;のようにループ内でROLLBACK TO(セーブポイントなしのROLLBACK含む)するとカーソルがクローズされて以降のFETCHが失敗します。PL/SQLレベルのFOR LOOPは多くの場合自動再オープンしますが、明示的なOPEN/FETCH/CLOSEパターンでは要注意。安全策として「ROLLBACK TOはBEGIN〜END副ブロックで例外捕捉時のみ使う」パターンを徹底してください。

業務シナリオ別の実戦パターン

SAVEPOINTを使う典型シナリオを4つ紹介します。いずれも実務で頻出する設計で、テンプレートとしてそのまま流用可能です。

パターン1|ループ内の行単位エラー隔離

1万件のCSV取り込み・APIバッチ処理など、1行失敗しても全体は止めたくない処理の標準形です。各反復の先頭でSAVEPOINTを打ち、例外時はその行だけROLLBACKしてログ記録します。

パターン1:行単位エラー隔離
DECLARE
  v_ok PLS_INTEGER := 0;
  v_ng PLS_INTEGER := 0;
BEGIN
  FOR rec IN (SELECT id, name, email FROM staging_data) LOOP
    SAVEPOINT before_row;
    BEGIN
      INSERT INTO customers(id, name, email)
      VALUES(rec.id, rec.name, rec.email);
      v_ok := v_ok + 1;
    EXCEPTION
      WHEN DUP_VAL_ON_INDEX THEN
        ROLLBACK TO before_row;
        INSERT INTO err_log(staging_id, reason, ts)
        VALUES(rec.id, 'duplicate', SYSTIMESTAMP);
        v_ng := v_ng + 1;
      WHEN OTHERS THEN
        ROLLBACK TO before_row;
        INSERT INTO err_log(staging_id, reason, ts)
        VALUES(rec.id, SUBSTR(SQLERRM,1,200), SYSTIMESTAMP);
        v_ng := v_ng + 1;
    END;
  END LOOP;

  COMMIT;
  DBMS_OUTPUT.PUT_LINE('OK=' || v_ok || ' / NG=' || v_ng);
END;
/

パターン2|複数ステップの段階的ロールバック

「処理1→処理2→処理3」のステップで、処理2で失敗したら処理1の結果は残し、処理2だけ取り消すという業務要件で活用するパターンです。

パターン2:段階的ロールバック
CREATE OR REPLACE PROCEDURE multi_step_process(
  p_order_id IN NUMBER
) AS
  e_validation_failed EXCEPTION;
BEGIN
  -- ステップ1:注文の在庫予約(必ず成功させたい)
  pkg_inventory.reserve(p_order_id);
  SAVEPOINT after_step1;

  -- ステップ2:与信チェック(失敗したら2だけ取消、1は残す)
  BEGIN
    pkg_credit.check_and_freeze(p_order_id);
  EXCEPTION
    WHEN pkg_credit.e_credit_exceeded THEN
      ROLLBACK TO after_step1;
      pkg_log.warn('credit failed for order=' || p_order_id);
      RAISE e_validation_failed;
  END;

  -- ステップ3:通知(失敗しても致命的ではない)
  BEGIN
    pkg_notify.send(p_order_id);
  EXCEPTION
    WHEN OTHERS THEN
      pkg_log.warn('notify failed for order=' || p_order_id);
      -- 通知失敗は無視して続行(ROLLBACKしない)
  END;

  COMMIT;
EXCEPTION
  WHEN e_validation_failed THEN
    -- ステップ1だけCOMMIT or ROLLBACK判断
    -- 在庫予約も含めて全部取り消すなら:
    ROLLBACK;
    RAISE_APPLICATION_ERROR(-20001, 'validation failed');
END;
/

パターン3|Sagaパターン(補償トランザクション)

分散トランザクションが使えない環境で「失敗時に逆操作で補償する」Sagaパターンを実装するときもSAVEPOINTが活きます。各ステップの前にSAVEPOINTを打ち、失敗時に直近まで戻して補償処理を走らせる形です。

パターン3:Saga風の補償処理
CREATE OR REPLACE PROCEDURE saga_book_travel(
  p_user_id IN NUMBER, p_trip_id IN NUMBER
) AS
  e_rollback_needed EXCEPTION;
BEGIN
  -- 1. ホテル予約
  pkg_hotel.book(p_user_id, p_trip_id);
  SAVEPOINT after_hotel;

  -- 2. 飛行機予約
  BEGIN
    pkg_flight.book(p_user_id, p_trip_id);
  EXCEPTION
    WHEN OTHERS THEN
      ROLLBACK TO after_hotel;
      pkg_hotel.cancel(p_user_id, p_trip_id);   -- 補償
      RAISE e_rollback_needed;
  END;
  SAVEPOINT after_flight;

  -- 3. 決済
  BEGIN
    pkg_payment.charge(p_user_id, p_trip_id);
  EXCEPTION
    WHEN OTHERS THEN
      ROLLBACK TO after_flight;
      pkg_flight.cancel(p_user_id, p_trip_id);  -- 補償
      pkg_hotel.cancel(p_user_id, p_trip_id);   -- 補償
      RAISE e_rollback_needed;
  END;

  COMMIT;
EXCEPTION
  WHEN e_rollback_needed THEN
    ROLLBACK;
    RAISE_APPLICATION_ERROR(-20002, 'saga failed, compensated');
END;
/

パターン4|複数の独立処理を一括実行(部分成功OK)

「10個のジョブを順次実行、失敗したジョブだけスキップして成功した分はCOMMIT」というパターン。各ジョブ前にSAVEPOINTを打ち、例外時にROLLBACK TOで巻き戻して次のジョブへ進みます。

パターン4:複数ジョブの部分成功
CREATE OR REPLACE PROCEDURE run_independent_jobs AS
  v_succeeded PLS_INTEGER := 0;
  v_failed    PLS_INTEGER := 0;
BEGIN
  FOR rec IN (SELECT job_id, job_proc FROM job_queue
              WHERE status = 'PENDING'
              ORDER BY priority DESC) LOOP
    SAVEPOINT before_job;
    BEGIN
      EXECUTE IMMEDIATE 'BEGIN ' || rec.job_proc || '; END;';
      UPDATE job_queue SET status = 'DONE'
       WHERE job_id = rec.job_id;
      v_succeeded := v_succeeded + 1;
    EXCEPTION
      WHEN OTHERS THEN
        ROLLBACK TO before_job;
        UPDATE job_queue
           SET status = 'FAILED',
               last_err = SUBSTR(SQLERRM,1,500),
               failed_at = SYSTIMESTAMP
         WHERE job_id = rec.job_id;
        v_failed := v_failed + 1;
    END;
  END LOOP;

  COMMIT;
  DBMS_OUTPUT.PUT_LINE('Succeeded: ' || v_succeeded || ' / Failed: ' || v_failed);
END;
/

JDBC/Java側のSavepoint連携

JavaやAPサーバから呼び出される処理では、JDBCのConnection.setSavepoint()でJava側からもSAVEPOINTを制御できます。Spring等のフレームワークでは@Transactional(propagation=NESTED)が内部的にJDBCのSavepointを使ってネスト実装されています。

Java/JDBCでのSavepoint連携
import java.sql.*;

try (Connection conn = DriverManager.getConnection(URL, USER, PASS)) {
    conn.setAutoCommit(false);

    try (PreparedStatement ps1 = conn.prepareStatement(
           "INSERT INTO customers VALUES(?, ?)")) {
        ps1.setLong(1, 100);
        ps1.setString(2, "Alice");
        ps1.executeUpdate();
    }

    // ★ JavaからSavepointを設定
    Savepoint sp1 = conn.setSavepoint("after_customer");

    try (PreparedStatement ps2 = conn.prepareStatement(
           "INSERT INTO orders VALUES(?, ?)")) {
        ps2.setLong(1, 1001);
        ps2.setLong(2, 100);
        ps2.executeUpdate();
    } catch (SQLException e) {
        // 注文だけ取消し、顧客は残す
        conn.rollback(sp1);
    }

    conn.commit();
}

SpringのNESTED伝播を使う場合、@Transactional(propagation=Propagation.NESTED)を付けたメソッドの入口で自動的にSAVEPOINTが打たれ、例外発生時にそのSAVEPOINTにROLLBACKされます。実装としてはJDBCのsetSavepoint()がそのまま使われており、PL/SQL側のSAVEPOINTとも互換的に動作します。ネストされたサービスメソッドの個別失敗を吸収する設計に向きます。

SAVEPOINT vs AUTONOMOUS_TRANSACTION|決定的な違いと使い分け

「親のトランザクションに影響を与えずに処理したい」要件でSAVEPOINTとAUTONOMOUS_TRANSACTIONはよく混同されますが、本質的に役割が異なります。正しく使い分けないと意図しない動作になります。

SAVEPOINT|同一トランザクション内のしおり

同じトランザクション内で戻り先を決める機能。親と子は同じトランザクションを共有しているため、ROLLBACKで全体が消えます。「途中の処理だけ取り消したいが全体としては1つのトランザクション」という場面に適します。

AUTONOMOUS_TRANSACTION|独立した別トランザクション

PRAGMA AUTONOMOUS_TRANSACTIONで宣言すると、そのプロシージャは呼び出し元と完全に独立した別トランザクションで動きます。親がROLLBACKされても自分のCOMMITは生き残り、その逆もまた然り。監査ログ・エラー記録など「親が失敗しても残したい」処理に使います。詳細はAUTONOMOUS TRANSACTIONで独立した処理を行う方法を参照してください。

判断基準

  • 「処理途中の一部だけ取消、続きは同じトランザクションで進める」→ SAVEPOINT
  • 「親が失敗しても必ず残したい記録/別系統の処理」→ AUTONOMOUS_TRANSACTION
  • 「親の進行中にデータを別経路で確認したい(ダーティリードしたい)」→ AUTONOMOUS_TRANSACTION(READ_COMMITTED分離レベル)
  • 「失敗時の補償処理を呼び出すため独立トランザクションが必要」→ AUTONOMOUS_TRANSACTION(先述のSagaパターンとは別軸)

混同で起きる事故:①「失敗時に必ず記録したいログ」をSAVEPOINTで実装→親のROLLBACKでログまで消えてしまう、②「途中状態を取消したい」をAUTONOMOUS_TRANSACTIONで実装→別トランザクションなので親側からは見えない・親のロックも持ち越せない、などの典型的な誤実装。「同一トランザクションか別トランザクションか」を最初に判定してから機能を選んでください。

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

① DDLを混在させてSAVEPOINTを失う

SAVEPOINTを打った後にCREATE/ALTER/TRUNCATEなどのDDLを実行すると暗黙コミットが発火して全SAVEPOINTが消えます。部分ロールバック設計のなかでDDLを実行する処理が必要なら別ジョブ・別トランザクションに切り出してください。6383(COMMIT/ROLLBACKの正しい使い方)の暗黙コミット節も合わせて確認してください。

② パッケージ変数の整合性を忘れる

ROLLBACK TOではDBの状態は戻りますがPL/SQL変数・パッケージ変数の値は戻りません。「失敗時にパッケージ変数の状態が中途半端」という事故が発生します。ROLLBACK後はパッケージ変数を明示的に再初期化するか、そもそもパッケージ変数に依存する設計を避けるのが安全です。

③ ループ中のFOR LOOP内でROLLBACK TO

カーソルがクリアされる挙動と相まって、FOR LOOPのカウンタが不定になる場合があります。行単位の例外隔離はループの中にBEGIN〜END副ブロックを作ってその中でROLLBACK TOする形が正解です。ループ自体のカーソル変数とROLLBACK TOを直接組み合わせないでください。

④ 同名SAVEPOINTを意図せず上書き

同じ名前で複数回SAVEPOINTすると最後の位置に上書きされ、初期に打ったしおりが失われます。SAVEPOINT名は意味のあるユニークな名前を付け、階層的な処理ではsp_step1sp_step2のように番号付きで区別してください。

⑤ シーケンスNEXTVALを呼んだ後にROLLBACK TO

シーケンスはトランザクションの外で動くため、NEXTVALで採番した番号はROLLBACK TOしても戻りません。「失敗で取り消したつもりが番号だけ消費された」状況になります。採番をやり直したい場合は別シーケンスを使うか、採番直前にSAVEPOINTを打って失敗を判別してから再採番する設計に。

⑥ AUTONOMOUS_TRANSACTIONと混同して使う

「親と独立したい」のにSAVEPOINTを使うのは目的違いの誤実装。「同一トランザクション内で戻したい」のがSAVEPOINT、「親と切り離したい」のがAUTONOMOUS_TRANSACTION。本記事の判断基準セクションを参考に、まず「同一か独立か」を決めてください。

よくある質問

QSAVEPOINTを多用するとパフォーマンスに影響しますか?
A影響はほぼありません。SAVEPOINTはセッション内のメタデータ更新だけで、ディスクI/Oも追加SQLも発生しないため極めて軽量です。ループ内で毎反復SAVEPOINTを打ってもオーバーヘッドは無視できる程度。気にせず必要な箇所に積極的に打って大丈夫です。
QCOMMITしたあとにROLLBACK TOで戻れますか?
A戻れません。COMMIT時点でトランザクションが終了し、SAVEPOINTもすべて消えます。部分ロールバックを使う場合はCOMMITは最後の1回にまとめてください。途中でCOMMITしたいならAUTONOMOUS_TRANSACTIONで別トランザクションに切り出すのが定石です。
Q同名のSAVEPOINTを複数回打っても問題ない?
A構文エラーにはなりませんが戻り先が上書きされるので意図しない動作になります。たとえばSAVEPOINT sp;を3回打つと、ROLLBACK TO spは最後に打った位置に戻ります。初期のしおりは無効化されるので、異なる位置にしおりが必要なら必ず別名で打ってください。
QSAVEPOINTで戻ったあとROLLBACKを呼ぶとどうなる?
A通常通り全体ROLLBACKが実行されます。ROLLBACK TO sp1でsp1まで戻った後のROLLBACKトランザクション開始時点まで戻すのでsp1以前の変更も消えます。部分ロールバックと全体ロールバックは別物として理解してください。
Q分散トランザクション(DBリンク経由)でもSAVEPOINTは使える?
Aローカル側の操作には使えますがDBリンク経由のリモートDMLはSAVEPOINTで戻せない場合があります。分散トランザクションは2フェーズコミットで管理されており、部分ロールバックの挙動は実装依存。分散環境ではAUTONOMOUS_TRANSACTIONやSagaパターンによる補償処理を組み合わせる方が安全です。
QFORALLの中でROLLBACK TOしたらバルク処理はどうなる?
AFORALLは1つのDMLとして実行されるため、SAVE EXCEPTIONS で個別行を保存している場合を除き、途中で部分的に取り消すことはできません。行単位のエラー処理が必要ならFORALL ... SAVE EXCEPTIONSを使い、失敗行をSQL%BULK_EXCEPTIONSから取得して個別処理します。SAVEPOINTとは別物の機能です。
QROLLBACK TO した後にDMLを続行できますか?
Aはい、トランザクションは継続中なのでDMLは続けられます。これがSAVEPOINTの最大の価値で、「失敗箇所だけ取り消して同じトランザクションで処理を続行」できます。ROLLBACK TO直後はテーブルの状態がSAVEPOINT時点に戻っているので、次に何を実行するかは前提条件を確認した上で決めてください。
QSAVEPOINTの数に上限はある?
AOracleには明示的な上限はありませんが、実装上は数千個まで現実的に扱えます。ただし「ループで毎反復100万回SAVEPOINTを打つ」のような極端な使い方は管理オーバーヘッドが見えてくる可能性があります。実務的には1トランザクションあたり数十〜数百個までが目安。それ以上必要な場面は設計を見直したほうが良いサインです。
QSAVEPOINTとロックの関係は?
ASAVEPOINT後に取得した行ロックはROLLBACK TOで解放されますが、SAVEPOINT前から保持しているロックは継続します。これによりトランザクション全体の排他性は維持されたまま部分的な変更だけ取り消せます。デッドロック対策として「ROLLBACK TOでロックを解放」する用途には使えないので注意してください。
QPL/SQLの例外を握りつぶすときにROLLBACK TOしないとどうなる?
A失敗したDMLが未確定のまま残る状態になり、次の処理に影響します。例外処理でROLLBACK TOを書かないと「予期しない中途半端なデータが見える」状態になりがち。EXCEPTION句では必ずROLLBACK TO sp_xxxRAISEのどちらかを書く規約を徹底してください。握りつぶし+無対処は最悪の組み合わせです。

関連記事で深掘りする

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

まとめ|SAVEPOINTで耐障害性の高いトランザクション設計を実装

SAVEPOINTは「失敗しても先へ進む」堅牢なトランザクション設計の中核です。内部動作(UNDO・SCN)の理解、ROLLBACK TOの正確な挙動、業務シナリオ別のパターン、AUTONOMOUS_TRANSACTIONとの使い分けを押さえれば、整合性を保ちながら部分失敗を許容する設計が組めます。本記事の要点を7つに集約します。

  1. SAVEPOINTは同一トランザクション内のしおり。AUTONOMOUS_TRANSACTIONと別物
  2. ROLLBACK TOで戻るのはDMLだけ。PL/SQL変数・シーケンスは戻らない
  3. 行単位エラー隔離は各反復先頭でSAVEPOINT+BEGIN〜END副ブロック
  4. 段階的ロールバックでステップ単位の失敗を吸収する設計が組める
  5. Sagaパターンで補償処理と組み合わせて分散風実装が可能
  6. JDBCのsetSavepoint()/SpringのNESTED伝播で言語側からも制御できる
  7. DDL混在で全SAVEPOINTが消える、カーソルクリアの罠など制限事項を理解

レガシーコードで「ROLLBACKで全部消えてバッチがやり直しになる」事故が頻発しているなら、SAVEPOINTで行単位エラー隔離に置き換えるだけで運用負荷が大きく下がります。本記事の4パターンを実装テンプレとして自プロジェクトのバッチ処理を見直してみてください。