データの整合性を守るトランザクション。コミットとロールバックの仕組み
データベースを扱う上で、「複数の更新処理を必ずセットで成功させなければならない」という場面は頻繁に発生します。この要求に応えるのがトランザクション(Transaction)の仕組みです。
なぜセットで成功させる必要があるのか
ネットショッピングを例に考えます。顧客が商品を注文した際、システムは以下の2つの処理を実行します。
ordersテーブルに注文レコードを追加するproductsテーブルの在庫数を減算する
これらは業務上、不可分な一対の処理です。しかし、1の処理が成功した直後にシステム障害やネットワークエラーが発生し、2の処理が失敗したとします。この場合、注文データは存在するのに在庫が減っていないという矛盾した状態が生まれます。
| テーブル | 状態 | 問題 |
|---|---|---|
orders |
注文レコードあり | 注文は受け付けられた扱い |
products |
在庫が変わらず | 実際には在庫が1つ減るべきだった |
この不整合が積み重なると、在庫の実数とシステム上の数値が乖離し、過剰受注や欠品の把握失敗につながります。
トランザクションとは何か
トランザクションとは、「一連の複数処理を、ひとつのまとまりとして扱う仕組み」です。
トランザクション内のすべての処理が成功した場合にのみ変更を確定し、いずれか一つでも失敗した場合はすべての変更をなかったことにします。この「全部成功か、全部取消か」という保証が、データの整合性を守る根幹です。
SQLでは以下のように記述します。
BEGIN;
INSERT INTO orders (customer_id, product_id, quantity)
VALUES (101, 55, 1);
UPDATE products
SET stock = stock - 1
WHERE product_id = 55;
COMMIT;
BEGIN; からトランザクションが開始され、COMMIT; で確定します。
COMMITとは何か
COMMIT(コミット)は、トランザクション内で行ったすべての変更を、データベースに正式に反映・確定する命令です。
COMMITが実行されて初めて、変更内容が他のユーザーやシステムから参照できる状態になります。それまでの変更はトランザクションを実行したセッション内でのみ見えており、確定はされていません。
BEGIN;
INSERT INTO orders (customer_id, product_id, quantity) VALUES (101, 55, 1);
UPDATE products SET stock = stock - 1 WHERE product_id = 55;
COMMIT; -- ここで初めてデータベースに確定される
COMMITはいわば「この一連の変更内容を正式に記録する」という宣言です。
ROLLBACKとは何か
ROLLBACK(ロールバック)は、トランザクション内で行ったすべての変更を取り消し、トランザクション開始前の状態に戻す命令です。
エラーが発生した場合や、処理途中で整合性の問題を検出した場合に使います。
BEGIN;
INSERT INTO orders (customer_id, product_id, quantity) VALUES (101, 55, 1);
-- 在庫チェック:在庫が0以下になる場合は処理を中断
UPDATE products SET stock = stock - 1 WHERE product_id = 55 AND stock > 0;
-- 更新が0件だった場合(在庫不足)はROLLBACKで全取消
ROLLBACK; -- 注文レコードの挿入も取り消される
ROLLBACKにより、注文レコードの挿入もなかったことになります。データベースはBEGIN;直前の状態に完全に戻ります。
COMMITとROLLBACKの対比
| 操作 | タイミング | 結果 |
|---|---|---|
| COMMIT | すべての処理が正常に完了したとき | 変更をデータベースに確定する |
| ROLLBACK | エラーや異常を検出したとき | すべての変更を取り消してもとに戻す |
エラーハンドリングとの組み合わせ
実際のアプリケーションコードでは、例外処理とトランザクションを組み合わせて使います。以下はPythonとPostgreSQLの例です。
try:
conn.execute("BEGIN")
conn.execute("INSERT INTO orders ...")
conn.execute("UPDATE products SET stock = stock - 1 ...")
conn.execute("COMMIT")
except Exception as e:
conn.execute("ROLLBACK")
raise e
正常に処理が終わればCOMMIT、例外が発生すればROLLBACKという構造が基本パターンです。多くのフレームワークやORMはこのパターンをラップした機能を標準で提供しています。
トランザクションが必要な場面
単純な1テーブルへの1件の更新であれば、トランザクションを明示的に記述しなくても問題になることは少ないです。しかし、以下の条件が重なる場合は必ずトランザクションで囲む設計が必要です。
- 複数のテーブルを連動して更新する(注文と在庫、送金の出金と入金など)
- 1つの処理が後続の処理の前提条件になっている
- 処理途中のデータが他のユーザーに見えてはならない
まとめ
トランザクション・コミット・ロールバックは、データベースにおける整合性保証の基本機能です。「複数の更新を一体として扱い、全成功か全取消かを保証する」という原則を押さえておくことで、設計上の見落としやデータ破損リスクを大きく減らすことができます。
この仕組みはACID特性の「原子性(Atomicity)」に相当し、エンタープライズシステムから小規模なWebアプリまで、データの信頼性を支える共通の基盤となっています。