Query Go
途中に戻せる地点を作る - SAVEPOINT の使い方・オプション・サンプル

途中に戻せる地点を作る - SAVEPOINT

トランザクション内に中間保存点を作り、部分的にロールバックできる仕組み

概念図

SAVEPOINT diagram

構文

sql
SAVEPOINT name; ... ROLLBACK TO SAVEPOINT name; RELEASE SAVEPOINT name;

サンプル

注文明細の登録だけ失敗してもトランザクション全体はやり直さず、SAVEPOINT 以降だけ巻き戻す

sql
BEGIN;
INSERT INTO orders (user_id, amount) VALUES (1, 1000);
SAVEPOINT before_items;
INSERT INTO order_items (order_id, sku) VALUES (100, 'A');
-- 在庫切れなど
ROLLBACK TO SAVEPOINT before_items;
INSERT INTO order_items (order_id, sku) VALUES (100, 'B');
COMMIT;

SAVEPOINT とは

SAVEPOINT は トランザクションの中に付ける「しおり」です。ROLLBACK TO SAVEPOINT name で、しおり以降の操作だけを取り消して、しおり時点の状態に戻せます。トランザクション全体は生き続けるので、そのまま別の処理を続行したり最終的に COMMIT したりできます。

主要な 3 つのコマンド

  • SAVEPOINT name: しおりを設定
  • ROLLBACK TO SAVEPOINT name: しおり以降を取り消す (しおりは残る)
  • RELEASE SAVEPOINT name: しおりを破棄 (操作は確定ではなく、単にしおりが無くなるだけ)

同じ名前で複数回 SAVEPOINT を張ると、前のしおりは隠されます (ネスト扱い)。

sql
BEGIN;
INSERT INTO logs(msg) VALUES ('step1');
SAVEPOINT s1;
INSERT INTO logs(msg) VALUES ('step2');
SAVEPOINT s2;
INSERT INTO logs(msg) VALUES ('step3');
ROLLBACK TO SAVEPOINT s2;   -- step3 だけ消える
RELEASE SAVEPOINT s1;        -- s1 を破棄
COMMIT;                      -- step1 と step2 が確定

真のネストトランザクションは SQL 標準には無い

多くの RDBMS では BEGIN の中で BEGIN を書いてもエラーか無視されます。ネストした「部分コミット」「部分ロールバック」が欲しいときは SAVEPOINT で実現します。

  • PostgreSQL: 内側の BEGIN は警告が出るだけで、SAVEPOINT に置き換わる動きはしない
  • SQL Server: BEGIN TRAN はネストできるが、内側の COMMIT は単にカウンタを減らすだけで実際の確定は一番外側でのみ行われる (誤解の元)
  • MySQL / Oracle: BEGIN のネストは不可。SAVEPOINT が唯一の手段

ORM / アプリでの利用パターン

多くの ORM (Rails ActiveRecord, Django, Hibernate, SQLAlchemy など) は「ネストしたトランザクション」を内部で SAVEPOINT に変換します。これにより、バッチ処理のなかで 1 件だけ失敗したらその 1 件だけスキップしつつ全体は継続する、といった実装が書けます。

sql
-- 擬似コード
BEGIN;
FOR row IN batch LOOP
  SAVEPOINT item;
  BEGIN
    INSERT ...;
  EXCEPTION WHEN OTHERS THEN
    ROLLBACK TO SAVEPOINT item;
    -- ログに残して次の行へ
  END;
END LOOP;
COMMIT;

関連トピック