PostgreSQLでは本当の意味でのネストされたトランザクションはないんだ。あるのは外側のトランザクションと、その中にある「セーブポイントの層」だけ。
「ネストされたトランザクション」って言葉は、PostgreSQLだと普通セーブポイント(savepoints)をSAVEPOINT、ROLLBACK TO SAVEPOINT、RELEASE SAVEPOINTコマンドで使うことを指すよ。これは独立した別々のトランザクションじゃなくて、1つの外側トランザクションの中にある特別なチェックポイントみたいなもので、全部をロールバックしなくてもそこまで戻れるんだ。
イメージしやすい例え:でっかいテキストを書いてて、時々ctrl+sで保存する感じ。もしミスったら、前の保存バージョンまで戻れるけど、全部の進捗が消えるわけじゃない。
セーブポイントを操作するコマンド
ネストされたトランザクションを管理するために、PostgreSQLは主に3つのコマンドを用意してる:
SAVEPOINT
このコマンドは「セーブポイント」を作るために使うよ。必要なときにそこまで戻れるチェックポイントみたいなもの。
SAVEPOINT mypoint;
ROLLBACK TO SAVEPOINT
指定したセーブポイント以降の一部の変更だけをロールバックして、それより前の変更はそのまま残すことができる。
ROLLBACK TO SAVEPOINT mypoint;
RELEASE SAVEPOINT
セーブポイントを削除するコマンド。これをやると、もうそのポイントには戻れなくなる。
RELEASE SAVEPOINT mypoint;
例:複数テーブルへのデータ追加とロールバックの使い方
例えば、注文管理システムを作ってて、ordersとorder_itemsの2つのテーブルに同時にデータを保存したいとする。どっちかのテーブルでエラーが起きても、もう一方のデータはロールバックしたくない場合があるよね。
BEGIN; -- トランザクション開始
-- セーブポイントを作成
SAVEPOINT before_order;
-- 注文をordersテーブルに追加
INSERT INTO orders (order_id, customer_id, date)
VALUES (1, 101, CURRENT_DATE);
-- ここでエラーが起きたらロールバック
SAVEPOINT before_order_items;
-- 商品をorder_itemsテーブルに追加
INSERT INTO order_items (order_id, product_id, quantity)
VALUES (1, 2001, 4);
-- もし何か問題があったら
-- ROLLBACK TO SAVEPOINT before_order_items;
-- トランザクションを確定(変更を保存)
COMMIT;
もしorder_itemsへの追加でエラーが発生したら、before_order_itemsまでロールバックできるし、ordersテーブルの変更はそのまま残るよ。
実践的なコツとよくあるミス
もうSAVEPOINTとROLLBACK TO SAVEPOINTの使い方が分かったと思うから、トラブルを避けるためのアドバイスをいくつか:
- セーブポイントの名前。
SAVEPOINTには分かりやすくてユニークな名前を付けよう。例えばbefore_insertとかstep1みたいにすると、デバッグのときに迷わないよ。 SAVEPOINTの解放を忘れずに。 もう戻る必要がないなら、RELEASE SAVEPOINTで消しておこう。トランザクションがごちゃごちゃしなくて済む。- ネストされたトランザクション ≠ 別トランザクション。
COMMITを実行すると、全部のセーブポイントが消えるから注意。外側のCOMMITが終わったら、もうロールバックはできないよ。 - データのロック。 セーブポイントまでロールバックしても、トランザクション内で取得したロックはそのまま残る。マルチユーザー環境だとこれが大事なポイントになるよ。
SAVEPOINTとROLLBACK TO SAVEPOINTを使ったネストされたトランザクションは、開発者にとって複雑な状況をうまく処理するための強力なツールだよ。これでトランザクションを柔軟に分割したり、エラーを丁寧に処理したり、無駄なロールバックを避けたりできる。ちなみに「ロールバック」って言葉を見ても、毎回焦る必要はないよ。時にはロールバックするのが一番前に進む方法だったりするからね。
GO TO FULL VERSION