タイトル: SAVEPOINT
SEOタイトル: SQL SAVEPOINT の使い方 - 部分ロールバック・ネストトランザクション完全ガイド
| この記事の要点 |
|
SAVEPOINT とは
SAVEPOINT は SQL 標準で定義されたトランザクション制御命令で、トランザクション内に名前付きの "チェックポイント" を作成します。エラー発生時に SAVEPOINT まで部分的にロールバックでき、その後の処理は継続可能です。
通常の ROLLBACK はトランザクション全体を巻き戻すのに対し、ROLLBACK TO SAVEPOINT は指定地点までの巻き戻しに留まります。これにより複雑な業務処理の一部だけをやり直すことができます。
基本構文
-- 設置
SAVEPOINT sp_name;
-- 巻き戻し(SAVEPOINT 自体は残り、再利用可能)
ROLLBACK TO SAVEPOINT sp_name;
ROLLBACK TO sp_name; -- SAVEPOINT 省略可(多くの DB)
-- 解放(明示的に SAVEPOINT を消す)
RELEASE SAVEPOINT sp_name;
典型的な使用例
3 段階の処理を行い、2 段階目がエラーでも 1 段階目は維持したいケース:
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 は無し
ROLLBACK との違い
| 命令 | 巻き戻し範囲 | トランザクションは継続するか |
|---|---|---|
ROLLBACK | BEGIN 以降すべて | 終了する |
ROLLBACK TO SAVEPOINT sp | SAVEPOINT 以降のみ | 継続する(再 COMMIT 可能) |
COMMIT | 巻き戻し無し(確定) | 終了する |
RELEASE SAVEPOINT sp | 巻き戻し無し(SAVEPOINT 削除のみ) | 継続する |
ネストして使う
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 が確定
注意: ROLLBACK TO sp2 を行うと、sp2 より後に作られた sp3 は自動的に消滅します。これは SQL 標準で規定された動作です。
DBMS 別の対応状況
| DBMS | 対応 | 特記事項 |
|---|---|---|
| PostgreSQL | 完全対応 | SQL 標準準拠。ROLLBACK TO sp 形式 OK |
| MySQL / MariaDB | 対応(InnoDB のみ) | MyISAM は不可。SAVEPOINT / ROLLBACK TO / RELEASE |
| Oracle | 完全対応 | SAVEPOINT / ROLLBACK TO SAVEPOINT。RELEASE は無し(自動管理) |
| SQL Server | 対応(独自構文) | SAVE TRANSACTION sp / ROLLBACK TRANSACTION sp |
| SQLite | 対応 | SQL 標準準拠 |
| DB2 | 対応 | SAVEPOINT sp ON ROLLBACK RETAIN CURSORS |
SQL Server の独自構文
-- 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;
エラーハンドリングとリトライ
外部 API 呼び出しや楽観ロックなど、失敗の可能性がある処理は SAVEPOINT を挟むと安全です:
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;
フレームワークでの利用
Spring (Java) のネストトランザクション
@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) { ... }
}
Spring は Propagation.NESTED 指定時に内部で Connection#setSavepoint() を呼び、SAVEPOINT ベースのネストを実現します。
Laravel (PHP) のネストトランザクション
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([...]);
});
よくあるトラブル
| 症状 | 原因 | 対処 |
|---|---|---|
ERROR: SAVEPOINT can only be used in transaction blocks | BEGIN していない | 事前に BEGIN; |
savepoint "sp" does not exist | 既に ROLLBACK で消滅した SAVEPOINT を参照 | 名前を見直し |
| 自動コミットモードで動かない | クライアントが auto-commit ON | SET autocommit = 0; |
| MySQL で MyISAM テーブル | 非トランザクションエンジン | InnoDB へ変換 |
FAQ
Q: SAVEPOINT を多用しても性能に影響しない?
A: 軽微な影響はあります。InnoDB / PostgreSQL ともに、各 SAVEPOINT で内部状態を記録するためメモリ消費が増えます。常識的な数(数十程度)なら問題ありません。
Q: 同じ名前の SAVEPOINT を再度 SAVEPOINT 文で作るとどうなる?
A: 多くの DB で古い SAVEPOINT が消滅し、新しい位置に作り直される動作になります(PostgreSQL は両方残る独自仕様)。挙動が DB によって違うので、名前は使い回さないのが安全です。
Q: COMMIT したら SAVEPOINT は?
A: トランザクション内のすべての SAVEPOINT はCOMMIT / ROLLBACK と同時に消滅します。トランザクションをまたぐ SAVEPOINT は存在しません。