トランザクションとは?COMMIT / ROLLBACK と ACID特性を初心者向けにやさしく解説

データベース

この記事では、トランザクションとは何か、COMMIT と ROLLBACK の違い、そしてデータベースの信頼性を支える ACID特性(原子性・一貫性・分離性・永続性)を、銀行振込の例えとコードでひと通り解説します。

対象は SELECT / UPDATE / INSERT / DELETE は書けるが、トランザクションは聞いたことがない初心者エンジニアです。前回のロックの記事の続編にあたる内容でもあります。

そもそも、なぜトランザクションが必要なのか

銀行の「振込」を例に考えます。AさんからBさんへ1万円を振り込むとき、データベースの中では次の2つの処理が走ります。

  1. A口座の残高から1万円を引く(UPDATE)
  2. B口座の残高に1万円を足す(UPDATE)

もし1番目だけ成功して、2番目の直前にシステムが落ちたらどうなるでしょうか。Aさんのお金は減ったのに、Bさんには届いていない──1万円がこの世から消えてしまいます。この2つは「両方成功」か「両方なかったことにする」のどちらかでなければなりません。

この「ひとまとまりの処理を、全部成功か全部取り消しかのどちらかにする」仕組みがトランザクションです。料理のレシピで「材料を全部そろえて作り切るか、何も作らないか」のどちらかにする、というイメージに近いです。


COMMIT と ROLLBACK の基本的な考え方

トランザクションは BEGIN で始まり、処理が問題なく終われば COMMIT で確定、途中で失敗すれば ROLLBACK で開始前の状態に巻き戻します。流れを整理すると次のとおりです。

  • BEGIN:トランザクションを開始する
  • COMMIT:すべての変更を確定・永続化し、ロックを解放する
  • ROLLBACK:すべての変更を取り消して開始前の状態に戻し、ロックを解放する

トランザクションは「全部確定(COMMIT)」か「全部取り消し(ROLLBACK)」かの二択で終わります。中途半端な状態がデータベースに残ることはありません。


トランザクションの基本構文

両方の UPDATE が成功した場合に COMMIT する例です。

BEGIN;                                   -- トランザクション開始
UPDATE account SET balance = balance - 10000 WHERE id = 'A';
UPDATE account SET balance = balance + 10000 WHERE id = 'B';
COMMIT;                                  -- 両方成功 → 確定

途中で異常を検知したら、COMMIT の代わりに ROLLBACK を実行します。

BEGIN;
UPDATE account SET balance = balance - 10000 WHERE id = 'A';
-- ここで残高不足などの異常を検知したら…
ROLLBACK;                                -- Aから引いた分も含めてすべて取り消し

COMMIT とは:「この一連の変更を確定して永続化する」命令です。実行すると変更が正式にディスクへ書き込まれ、障害が起きても消えない状態になり、他のトランザクションからも見えるようになります。前回学んだロックも、このタイミングで解放されます。
ROLLBACK とは:「この一連の変更をすべて取り消して、開始前の状態に戻す」命令です。BEGIN 以降に行った UPDATE や INSERT がなかったことになります。ロックも同様に解放されます。プログラムでは「エラーが起きたら ROLLBACK、問題なければ COMMIT」という形で使うのが基本です。


トランザクションの4つの約束「ACID特性」

トランザクションがデータの信頼性を守るために満たすべき性質を、頭文字を取って ACID特性 と呼びます。難しそうな用語ですが、ひとつずつ見れば素直な内容です。

頭文字特性名意味振込の例
AAtomicity(原子性)全部実行されるか、まったく実行されないか(オール・オア・ナッシング)「出金と入金が両方」か「両方なし」。片方だけは起きない
CConsistency(一貫性)トランザクションの前後で、決められたルール(制約)が常に守られている「残高はマイナスにならない」などの制約が破られない
IIsolation(分離性)同時に走る別のトランザクションから影響を受けず、互いに邪魔をしない他人の振込処理が、自分の振込の途中経過を読んで混乱しない
DDurability(永続性)一度 COMMIT した結果は、その後障害が起きても失われない振込完了後に停電しても、記録は残っている

原子性(A)は COMMIT / ROLLBACK で実現され、分離性(I)は前回学んだロックなどで実現されます。つまりロックとトランザクションは別物ではなく、トランザクションの「分離性」を支える部品がロック、という関係です。


分離レベル(応用)

分離性(Isolation)は、実は「どこまで厳密に分離するか」を分離レベルとして段階的に選べます。厳しくするほど安全ですが、待ちが増えて遅くなります。代表的なものは次の4段階です。

分離レベル概要
READ UNCOMMITTED他人の未確定の変更まで見えてしまう(最も緩い)
READ COMMITTED確定済みの変更だけ見える(多くのDBの既定値)
REPEATABLE READ同じ行を何度読んでも同じ結果になる
SERIALIZABLEまるで1件ずつ順番に実行したかのように振る舞う(最も厳しい)

分離レベルが緩いと、「未確定データを読む(ダーティリード)」などの好ましくない現象が起こりえます。初心者のうちは「厳しくするほど安全だが遅くなる、というトレードオフがある」とだけ押さえておけば十分です。


実例:プログラムでの典型的な使い方(Python)

実際のプログラムでは、正常終了したら COMMIT、例外が起きたら ROLLBACK という形で書きます。Python(psycopg2)を使った例です。

conn = psycopg2.connect("dbname=bank")
cur = conn.cursor()
try:
    cur.execute("UPDATE account SET balance = balance - 10000 WHERE id = 'A';")
    cur.execute("UPDATE account SET balance = balance + 10000 WHERE id = 'B';")
    conn.commit()    # 両方成功 → 確定
except Exception:
    conn.rollback()  # 途中で失敗 → すべて取り消し
    raise
finally:
    conn.close()

この形にしておけば、処理の途中で何が起きても「中途半端な状態」が残りません。with 構文やフレームワークの仕組みを使えば、commit / rollback を自動化することもできます。


まとめ

  • トランザクションは、ひとまとまりの処理を「全部成功か、全部取り消しか」にする仕組み
  • BEGIN で始め、成功なら COMMIT(確定・永続化)、失敗なら ROLLBACK(開始前に巻き戻し)
  • 信頼性を支える4つの約束が ACID(原子性・一貫性・分離性・永続性)
  • 分離性はロックなどで実現され、分離レベルで安全性と速度のバランスを選べる

最後にひとつ疑問が残ります。「COMMIT した結果が、停電しても消えない(=永続性)」のは、いったいどうやって実現しているのでしょうか。その裏側を支えるのが WAL(先行書き込みログ) です。次回はそのWALと、データベースの内部動作を掘り下げます。


参考リソース

コメント

タイトルとURLをコピーしました