SAVEPOINTとサブトランザクション:PostgreSQLの「安全なやり直し」を使いこなす
現場でPostgreSQLを触っていると、「ここまでは処理したいけど、もし失敗したらそこだけ無かったことにしたい……」なんて場面、一度は遭遇するよね。
例えば、大量のデータを一括でインポートするバッチ処理で、途中の1レコードだけバリデーションエラーで弾かれたとする。そのたびにトランザクション全体をロールバックして、最初からやり直し……なんてやってたら、運用がパンクしちゃうよ。
そんな時に頼りになるのが「サブトランザクション(SAVEPOINT)」だ。今日は、こいつの仕組みと、実務でハマらないためのコツを少し深掘りしてみよう。
—
サブトランザクションって、結局なんなの?
一言で言うと、「トランザクションという『大きな箱』の中に作れる『小さな隠し部屋』」だ。
親トランザクションの中で `SAVEPOINT` を発行すると、そこがチェックポイントになる。もしその後の処理でエラーが起きても、そのチェックポイントまで状態を戻すことができる。面白いのは、その「戻した」という事実を親トランザクションは知っていて、そのまま処理を継続できるところだ。
BEGIN;
INSERT INTO logs (msg) VALUES (‘処理開始’);
SAVEPOINT sub1;
INSERT INTO users (name) VALUES (‘太郎’);
— ここで何かの理由でエラー発生!
— ROLLBACK TO SAVEPOINT sub1; を実行
ROLLBACK TO SAVEPOINT sub1;
INSERT INTO logs (msg) VALUES (‘太郎の登録は失敗したけど、処理は続けるよ’);
COMMIT;
これ、すごく便利そうに見えるでしょ? でも、データベースエンジニアとしては、一つだけ強く伝えておきたいことがある。
—
気をつけるべき「罠」:サブトランザクションは魔法じゃない
実務でサブトランザクションを多用する前に、これだけは覚えておいてほしい。「サブトランザクションを乱用すると、PostgreSQLのパフォーマンスは劇的に落ちる」という事実だ。
1. ログとメモリへの負荷
PostgreSQLはサブトランザクションの深さや数を追跡するために、メモリ上の構造体を使ったり、特定のフラグをWAL(Write Ahead Log)に書き込んだりする。数個なら全く問題ない。でも、数千、数万ものサブトランザクションをネストさせると、メモリ消費量が増大し、コミット時の処理負荷が跳ね上がる。
2. 可視性の管理(MVCC)
PostgreSQLのMVCC(多版同時実行制御)において、サブトランザクションは「誰がそのデータを見たか」という判定を複雑にする要因になる。大量のサブトランザクションを作ると、その分だけシステムカタログやインデックスのクリーンアップ(autovacuum)が効率的に進まなくなることがあるんだ。
—
実務での賢い使いどころ
じゃあ、いつ使うのが正解か?
一番おすすめなのは、「冪等性が担保しにくい、あるいは再試行コストが高い処理の防波堤」として使うことだ。
例えば、外部APIを叩いたり、複雑な計算を伴うマスターデータの更新をループ内で回す場合。メインの処理は一気に進めたいけれど、特定の条件下でのみ失敗するリスクがあるなら、そのブロックだけ `SAVEPOINT` で囲うのが非常にスマートだ。
Python/psycopg2でのイメージ
try:
with conn:
with conn.cursor() as cur:
cur.execute(“INSERT INTO main_table …”)
# 部分的な失敗を許容するブロック
cur.execute(“SAVEPOINT sp1”)
try:
cur.execute(“INSERT INTO secondary_table …”)
except Exception:
cur.execute(“ROLLBACK TO SAVEPOINT sp1”)
# ここでログを出したり、別のリカバリ処理を行う
cur.execute(“RELEASE SAVEPOINT sp1”) # 明示的に開放するのも忘れずに
except Exception:
conn.rollback()
—
最後に:先輩からのアドバイス
サブトランザクションは、「失敗しても大丈夫」という安心感を与えてくれる強力なツールだ。でも、「SAVEPOINTを使わなくても済む設計」ができないか、一度立ち止まって考えてみてほしい。
例えば、ループの外でバリデーションを済ませる、あるいは処理を細かく分割して個別のトランザクションで実行するなど、アプローチを変えるだけで性能上の懸念は消えることが多い。
「とりあえずSAVEPOINTで囲めばいいや」という安直な選択は、半年後の自分や、そのコードを引き継ぐメンバーを苦しめることになる。サブトランザクションは、あくまで「最後の手段」としてポケットに忍ばせておく、くらいがちょうどいいんだ。
現場からは以上!なにか具体的に「こういうケースで迷ってる」っていうのがあれば、またいつでも相談してね。
コメント