◀ 3.

Oracle 19cのSAVEPOINT|部分ロールバック・COMMIT・DDLの境界

Oracle Database 19c の SAVEPOINT は、同じトランザクション内で変更の一部を取り消すための目印です。ROLLBACK TO はそこより後の変更を戻しますが、トランザクションを終了しません。

この記事の要点
  • Oracle 19c は SAVEPOINT と ROLLBACK TO を使います。RELEASE SAVEPOINT は Oracle の構文ではありません。
  • Oracle のトランザクション開始に SQL の BEGIN は不要です。PL/SQL の BEGIN は処理ブロックの開始で、SQL の BEGIN TRANSACTION ではありません。
  • 全体の COMMIT / ROLLBACK は保存点を消します。通常の DDL やクライアントの自動コミットも境界になります。
  • 保存点を重ねても独立した内部トランザクションにはなりません。外部決済の取消も保証しません。

Oracle SAVEPOINT:部分取消と未確定の変更、全体終了と製品別RELEASE構文の違い

SAVEPOINT とは

トランザクション制御のうち、全体の ROLLBACK と部分的な ROLLBACK TO を使い分けるための機能です。Oracle は最初の実行可能な SQL によりトランザクションを暗黙に開始します。同名の保存点を作り直すと、その名前は新しい位置を指します。Oracle 19c SAVEPOINT

基本構文

同じ接続の、まだ COMMIT していない処理の中で使用します。次の二つの ROLLBACK TO は代替表記です。

SAVEPOINT sp_name;
ROLLBACK TO SAVEPOINT sp_name;
-- 同じ意味の別表記: ROLLBACK TO sp_name;

保存点が作られた後の DML が取り消され、指定点とそれ以前の保存点は残ります。指定点より後の保存点は消えます。同じ指定点へ繰り返し戻れます。Oracle 19c ROLLBACK

実習前の準備

以下は実行結果を掲載したものではなく、Oracle 19c 用の教材です。使い捨ての専用スキーマで、自分の表を作成・参照・更新でき、以下の名前が未使用であることを前提にします。他の未確定作業がない接続で準備を終え、実習中は同じ接続を使います。DDL を実習の途中へ挟みません。

CREATE TABLE w38_sp_customers (
    id NUMBER PRIMARY KEY,
    name VARCHAR2(100) NOT NULL
);
CREATE TABLE w38_sp_addresses (
    customer_id NUMBER REFERENCES w38_sp_customers(id),
    addr VARCHAR2(100)
);
CREATE TABLE w38_sp_points (
    customer_id NUMBER REFERENCES w38_sp_customers(id),
    point NUMBER
);
CREATE TABLE w38_sp_orders (
    id NUMBER PRIMARY KEY,
    label VARCHAR2(10)
);

クライアントの自動コミットを無効にします。SQL*Plus なら次は SQL ではなくクライアントコマンドです。他のツールや JDBC では、その接続の設定方法を確認してください。

SET AUTOCOMMIT OFF

典型的な使用例

顧客登録は維持し、任意の住所登録だけを取り消して、ポイント登録へ進む例です。表は準備直後の空の状態を前提にします。エラーを実際に起こす例ではなく、取消分岐を明示的に通します。

INSERT INTO w38_sp_customers (id, name) VALUES (1, 'Alice');
SAVEPOINT after_customer;
INSERT INTO w38_sp_addresses (customer_id, addr) VALUES (1, 'Tokyo');
ROLLBACK TO after_customer;
INSERT INTO w38_sp_points (customer_id, point) VALUES (1, 100);

SELECT id, name FROM w38_sp_customers ORDER BY id;
SELECT customer_id, addr FROM w38_sp_addresses ORDER BY customer_id;
SELECT customer_id, point FROM w38_sp_points ORDER BY customer_id;
ROLLBACK;

SELECT 時点の期待状態は顧客1件、住所0件、ポイント1件です。最後は実習用に全体を ROLLBACK するため、顧客とポイントも取り消されます。業務で保存する場合の COMMIT はこれとは別の選択です。

ROLLBACK との違い

命令変更への作用継続と保存点
ROLLBACK現在のトランザクションの未確定変更全体を取消終了し、全保存点を消す
ROLLBACK TO SAVEPOINT spsp より後だけ取消継続。sp と以前の点を保持、後の点を消す
COMMIT現在のトランザクション全体を確定終了し、全保存点を消す
RELEASE SAVEPOINT sp対応製品で保存点を解放。変更取消とは別Oracle 19c では使用不可。比較先ごとの境界に注意

Oracle の通常の DDL は、構文として有効な文の実行前に暗黙 COMMIT を行い、成功後にも COMMIT します。実行エラーになった DDL でも、直前の DML が未確定のまま残るとは限りません。自動コミットが無効でもこの境界は別に存在します。Oracle 19c COMMIT

ネストして使う

準備した空の w38_sp_orders を使います。保存点の重なりを観察する例で、独立した内部 COMMIT の例ではありません。

INSERT INTO w38_sp_orders (id, label) VALUES (1, 'A');
SAVEPOINT sp1;
INSERT INTO w38_sp_orders (id, label) VALUES (2, 'B');
SAVEPOINT sp2;
INSERT INTO w38_sp_orders (id, label) VALUES (3, 'C');
SAVEPOINT sp3;
INSERT INTO w38_sp_orders (id, label) VALUES (4, 'D');
ROLLBACK TO sp3;
SELECT id, label FROM w38_sp_orders ORDER BY id;
ROLLBACK TO sp2;
SELECT id, label FROM w38_sp_orders ORDER BY id;
ROLLBACK;

最初の SELECT は1・2・3、次は1・2が残る期待です。sp2 へ戻った後に sp3 は参照できません。sp2 は残ります。最後の全体 ROLLBACK は1・2も戻します。

DBMS 別の対応状況

製品・参照範囲保存点の操作区別したい点
PostgreSQL 18SAVEPOINT / ROLLBACK TO / RELEASE SAVEPOINTSQL のトランザクションブロック内。同名の古い点は新しい同名点を解放すると再び参照可能
MySQL 8.4 / MariaDB公式資料InnoDB で SAVEPOINT / ROLLBACK TO / RELEASE SAVEPOINT非トランザクション表の変更を取消できるという意味ではない。全エンジンを同一扱いにしない
Oracle 19cSAVEPOINT / ROLLBACK TO [SAVEPOINT]RELEASE SAVEPOINT なし
SQL ServerSAVE TRANSACTION / ROLLBACK TRANSACTIONOracle の構文と異なる
SQLite公式資料SAVEPOINT / ROLLBACK TO / RELEASE外側の BEGIN がなくても開始可能。最外の RELEASE はトランザクション確定となり得る
Db2旧例は SAVEPOINT sp ON ROLLBACK RETAIN CURSORSこの構文の対象製品・版は未確認。実行前に該当版の資料で確認する

MySQL、MariaDB、PostgreSQL、SQLiteを比較しています。単なる「SQL標準対応」というラベルから、保存点の寿命まで同じと判断しないでください。

SQL Server の独自構文

既存例は末尾の「既存コードの製品別補足」S4に残しています。orders(id, label) のような2列の空の教材表、主キー重複のない値、未開始の専用接続を前提にします。BEGIN TRANSACTION / SAVE TRANSACTION / ROLLBACK TRANSACTION は T-SQL の比較で、Oracle に貼り付ける手順ではありません。Microsoft SAVE TRANSACTION

エラーハンドリングとリトライ

保存点はDB内の取消位置を作りますが、接続切断やトランザクション全体が失われた場合にも継続できる保証はありません。エラー分類と現在のトランザクション状態を確認し、再試行するなら同じ処理の二重実行も考慮します。

既存の決済例S5は、SQLのBEGIN、PL/pgSQLのPERFORM、例外ブロック内のROLLBACK TOを混ぜた断片です。Oracleの実行例ではなく、PostgreSQL 18でもPL/pgSQL内の保存点コマンドはサポートされません。外部決済はDBのROLLBACK TOで取り消せません。決済側の状態照会・冪等性・取消手段を別途設計する必要があります。PostgreSQL PL/pgSQL transaction management

フレームワークでの利用

Spring (Java) のネストトランザクション

S6の二つのサービスは、別Beanのトランザクションプロキシを経由する概念例です。import、依存注入、Order/StockException等の定義を省略しており、そのままコンパイルする完成例ではありません。Spring 6.2のNESTEDは単一物理トランザクションの保存点を利用し、通常JDBCリソース向けです。使用するtransaction managerとドライバの対応が前提です。REQUIRES_NEWは独立した物理トランザクションで、外側のROLLBACKと運命を共有する保存点ではありません。Spring公式説明

Laravel (PHP) のネストトランザクション

S7は[...]の列・値を省略した構造例です。Laravel 13の実装はgrammarのsupportsSavepoints判定を使います。内側の終了が外側と独立して物理COMMITされるわけではありません。同一connection、対応driver/grammar、例外発生時の状態が前提で、Oracleドライバとの動作確認をした例ではありません。すべてのThrowableを握りつぶして処理継続できるという意味でもありません。Laravel公式ソース

よくあるトラブル

症状確認する境界対処の考え方
SAVEPOINT can only be used in transaction blocksPostgreSQLのSQLトランザクション外の例PostgreSQLのBEGINが必要な場面。OracleにBEGINを追加する指示ではない
savepoint "sp" does not exist名前、接続、全体終了、後方点へのROLLBACK TO点が消えた原因を確認。名前だけ直して復旧を保証しない
自動コミットで保存点が使えない使用クライアント・接続のcommit設定接続設定を確認。SET autocommit=0をOracle SQLとして使わない
MySQLでMyISAMを使用表のストレージエンジン非トランザクションの変更は取消対象にできない。既存表の変換を自動対処にしない

FAQ

多数の保存点は性能に影響する? 保存点数だけで許容性能を決めることはできません。「数十なら問題ない」という一律の閾値は設けず、対象DB・処理・負荷で判断します。ここに実測値はありません。

同じ名前を再使用すると? Oracle 19cでは以前の同名点は消え、新しい位置になります。PostgreSQLの同名点の扱いとは異なります。

COMMIT後に戻れる? 保存点は消えます。保存点はバックアップではなく、トランザクションをまたぐ復元手段ではありません。

既存コードの製品別補足

以下は元記事の7個のコード資産を、用途と制限を付けて保持したものです。Oracle 19cの実習には本文のw38_sp_*例を使い、別製品・不完全な断片を一続きに実行しないでください。

S1

S1: 既存の構文比較。RELEASE SAVEPOINTはMySQL/PostgreSQL等の比較でありOracle19cでは使えません。実行可能な一連の処理ではなく、保存点名を使う構文断片です。ROLLBACK TOの2行は代替表記です。

-- 設置
SAVEPOINT sp_name;

-- 巻き戻し(SAVEPOINT 自体は残り、再利用可能)
ROLLBACK TO SAVEPOINT sp_name;
ROLLBACK TO sp_name;          -- SAVEPOINT 省略可(多くの DB)

-- 解放(明示的に SAVEPOINT を消す)
RELEASE SAVEPOINT sp_name;

S2

S2: SQLのBEGINを持つ旧比較例。Oracle実習は本文のw38_sp_*版を使います。比較先ではcustomers(id,name)、addresses(customer_id,addr)、points(customer_id,point)と必要な制約・未使用id=1、自動コミット無効の専用接続が必要です。INSERT成功後に明示的取消を通す例で、任意のSQLエラーから復帰する完成処理ではありません。COMMITは実際の確定です。

BEGIN;

-- 1段階目: 顧客登録
INSERT INTO customers (id, name) VALUES (1, 'Alice');
SAVEPOINT after_customer;

-- 2段階目: 任意の住所登録(エラー許容)
INSERT INTO addresses (customer_id, addr) VALUES (1, 'Tokyo');
-- もし制約違反やビジネスロジックで失敗したら
ROLLBACK TO after_customer;
-- → addresses への INSERT は消える、customers は残る

-- 3段階目: ポイント付与(必須)
INSERT INTO points (customer_id, point) VALUES (1, 100);

COMMIT;
-- → customers と points は確定、addresses は無し

S3

S3: SQLのBEGINを持つ旧比較例。ordersがidと文字列の2列でid=1から4が未使用、明示トランザクションと保存点を扱う製品の専用接続が前提です。Oracle実習は本文を使用します。保存点の重なりであり独立COMMITではなく、SQL標準全製品の細部の証明でもありません。

BEGIN;

INSERT INTO orders VALUES (1, 'A');
SAVEPOINT sp1;

  INSERT INTO orders VALUES (2, 'B');
  SAVEPOINT sp2;

    INSERT INTO orders VALUES (3, 'C');
    SAVEPOINT sp3;

    INSERT INTO orders VALUES (4, 'D');
    ROLLBACK TO sp3;
    -- 4 のみ消える。1,2,3 は残る

  ROLLBACK TO sp2;
  -- 3 も消える。1,2 が残る(sp3 も自動消滅)

COMMIT;
-- 1, 2 が確定

S4

S4: SQL Serverの比較。空のorders(id,label)教材表、idの一意性、権限、他の開始済みトランザクションのない専用接続が前提です。Oracle用ではありません。末尾COMMITは保存します。

-- SQL Server は SAVE TRANSACTION
BEGIN TRANSACTION;

INSERT INTO orders VALUES (1, 'A');
SAVE TRANSACTION sp1;

INSERT INTO orders VALUES (2, 'B');
ROLLBACK TRANSACTION sp1;    -- sp1 以降ロールバック

COMMIT TRANSACTION;

S5

S5: 実行不可の旧決済ハイブリッド断片です。OracleはPERFORMを持たず、PG18 PL/pgSQL内でもSAVEPOINT/ROLLBACK TOはサポートされません。orders列定義やcall_payment_apiも未定義です。SQL/手続き言語の境界と例外後の状態が欠けており、完成例として使いません。DB保存点は外部決済の取消や安全な再送を保証しません。

BEGIN;

INSERT INTO orders (id, total) VALUES (100, 5000);
SAVEPOINT before_payment;

BEGIN
  -- 外部決済 API 呼び出し(失敗するかもしれない)
  PERFORM call_payment_api(100);
EXCEPTION WHEN OTHERS THEN
  ROLLBACK TO before_payment;
  -- 失敗時は order を「未払い」状態で残す
  UPDATE orders SET status = 'PAYMENT_FAILED' WHERE id = 100;
END;

COMMIT;

S6

S6: Springの省略例です。別サービスBeanのプロキシ経由、対応するJDBC transaction manager/driverと保存点、依存注入、Order/StockException/log/import等の定義が必要です。{ ... }を持ち未完成です。文字列の"等は旧表示の残存エスケープでJava文字列として実行しません。NESTEDは単一物理トランザクションの保存点、REQUIRES_NEWは独立した物理トランザクションです。

@Service
public class OrderService {

    @Transactional
    public void placeOrder(Order o) {
        customerService.register(o.getCustomer());
        try {
            inventoryService.reserve(o);   // NESTED
        } catch (StockException e) {
            // 在庫不足 → reserve だけ巻き戻し、registerは生きる
            log.warn("Stock failure, continue without reservation");
        }
    }
}

@Service
public class InventoryService {
    // 親トランザクションに SAVEPOINT を作る
    @Transactional(propagation = Propagation.NESTED)
    public void reserve(Order o) { ... }
}

S7

S7: Laravelの省略例です。[...]は実行可能な入力データではありません。同一connection、対応driver/grammar、customers/addresses/pointsと列・値・制約が必要です。すべてのThrowable後に継続できる保証はなく、Oracleドライバの動作検証例でもありません。

DB::transaction(function () {
    DB::table('customers')->insert([...]);

    try {
        // ネスト = 内部で SAVEPOINT 発行
        DB::transaction(function () {
            DB::table('addresses')->insert([...]);
        });
    } catch (Throwable $e) {
        // addresses INSERT のみロールバック
        Log::warning('addr failed but continue');
    }

    DB::table('points')->insert([...]);
});
子ページ

子ページはありません

同階層のページ
  1. COMMIT (トランザクション制御)
  2. ROLLBACK
  3. SAVEPOINT

最近更新/作成されたページ