メインコンテンツへ移動
SEMentor
データベース 約12分 データベース curriculum

トランザクションとACIDの理解

データの整合性を守るトランザクションの仕組みと、ACID特性が実際の障害でどう機能するかを理解する

トランザクションとは

銀行振込を想像してください。「口座Aから1万円を引き落とし、口座Bに1万円を入金する」という操作は、2つのSQL文で構成されます。

UPDATE accounts SET balance = balance - 10000 WHERE id = 'A';
UPDATE accounts SET balance = balance + 10000 WHERE id = 'B';

もし1文目の実行後、2文目の直前にシステムがクラッシュしたら? 1万円が宙に浮いて消えます。

これを防ぐのがトランザクションです。「2つの操作をひとまとめにして、全部成功するか全部なかったことにする」という仕組みです。

BEGIN;
  UPDATE accounts SET balance = balance - 10000 WHERE id = 'A';
  UPDATE accounts SET balance = balance + 10000 WHERE id = 'B';
COMMIT;  -- 全部成功なら確定
-- ↑ いずれかが失敗すれば ROLLBACK(自動または手動)

ACID特性

トランザクションが保証すべき4つの性質をACIDと呼びます。

A — 原子性(Atomicity)

「全部成功か全部失敗」です。途中の状態は存在しません。

上記の振込例がまさにこれです。引き落としと入金は一体であり、片方だけが完了することはありません。

C — 整合性(Consistency)

トランザクション実行前後で、データが定義したルールを満たしている状態を保ちます。

例:「残高は0以上でなければならない」という制約がある場合、残高を負にするトランザクションは拒否されます。

I — 独立性(Isolation)

複数のトランザクションが同時実行されても、互いに影響を与えません。

あなたが残高を確認している最中に、別のトランザクションが金額を書き換えても、あなたの参照結果は一貫した状態を保ちます。

D — 耐久性(Durability)

コミットされたデータは、その後のシステム障害が起きても永続的に保持されます。

これを実現するのが**WAL(Write-Ahead Logging)**です。変更内容をデータファイルに書き込む前に、まずログファイルに記録します。障害後のリカバリ時にログを再適用して整合性を回復します。

分離レベルと発生する問題

独立性(I)は「完全な独立」を実現するほど性能が低下します。そのため、実用上は4段階の分離レベルから選びます。

分離レベルを下げると発生する問題

問題内容
ダーティリードコミット前のデータを別トランザクションが読んでしまう
ノンリピータブルリード同一トランザクション内で同じ行を2回読むと、値が変わっている
ファントムリード同一トランザクション内で同じ条件で検索すると、行数が変わっている

分離レベルの比較

分離レベルダーティリードノンリピータブルファントム
READ UNCOMMITTED発生発生発生
READ COMMITTED防止発生発生
REPEATABLE READ防止防止発生
SERIALIZABLE防止防止防止

多くのRDB(PostgreSQLなど)のデフォルトは READ COMMITTED です。MySQLのInnoDBは REPEATABLE READ です。

実際の開発では、デフォルトの分離レベルを把握した上で、問題が起きうる箇所に SERIALIZABLE を適用するか、楽観的ロックを使って対処します。

ロックとデッドロック

独立性を保つ方法のひとつがロックです。あるトランザクションがデータを更新している間、他のトランザクションの読み書きをブロックします。

しかし、2つのトランザクションが互いに相手のロック解除を待ち続けるデッドロックが発生することがあります。

トランザクションA: テーブル1をロック → テーブル2を待機
トランザクションB: テーブル2をロック → テーブル1を待機
(永遠に進めない)

RDBはデッドロックを検出し、片方を強制ロールバックして解消します。

デッドロックを防ぐ実践的な対策:

  • 複数テーブルを更新する順番を、アプリケーション全体で統一する
  • トランザクションをできる限り短くする
  • 更新が少ない参照系クエリには SELECT ... FOR SHARE を使う

初心者から中級者へ:どこまでを1つのトランザクションにするか

初心者が迷いやすいのは、「どこからどこまでを1つのトランザクションとして扱うべきか」です。判断基準は、途中で失敗したときにデータとして矛盾するかどうかです。

処理まとめるべきか理由
注文作成と注文明細作成まとめる片方だけ残ると注文として壊れる
残高減算と入金加算まとめる金額の整合性が崩れる
ユーザー登録と確認メール送信DB更新はまとめるが、メールは分けることが多い外部送信はロールバックできない
ログ出力通常は分ける本処理の失敗で監査ログまで消すと追跡しづらい

中級者は、DBのトランザクションだけでなく、メール送信・外部API・キュー投入のような「ロールバックできない副作用」も含めて設計します。

実務での設計メモ

  • トランザクション中にユーザー入力待ちや外部API待ちを入れない
  • 更新順序をチームで統一し、デッドロックを減らす
  • 失敗時に再実行してよい処理は、冪等性を持たせる
  • 長いバッチ処理は、小さな単位に分割してコミットする
  • 重要処理は、成功・失敗・ロールバック理由をログに残す

トランザクションは「とりあえずBEGINで囲む」ものではありません。守るべき整合性と、待たせてはいけない処理を分けて設計しましょう。

まとめ

用語ひと言まとめ
トランザクション一連の操作を一体として扱う仕組み
Atomicity全部成功か全部失敗
Consistency前後でデータルールを守る
Isolation同時実行でも互いに影響しない
Durabilityコミット後は障害でも失われない
分離レベル独立性と性能のトレードオフを調整する設定
デッドロック互いがロック待ちになる状態

「なぜ本番でたまにデータが壊れるのか」「なぜ特定の処理が止まるのか」という問題の多くは、トランザクションと分離レベルの理解で解決できます。


RDBの基本的な操作と設計についてはリレーショナルDBとSQL基礎で学べます。

Beginner to Intermediate

トランザクションとACIDの理解を実務につなげる学び方

データベースは、言葉を知るだけでなく「どこで使う知識か」まで結びつけると定着します。初学者は全体像をつかみ、中級者は切り分けや設計判断に使える形へ伸ばしていきましょう。

初心者の到達点

  • テーブル、行、列、主キー、外部キーを、業務データの関係として説明する。
  • SELECT文を読むときは、取得対象・条件・並び順・集計の順で意味を確認する。

中級者の観点

  • インデックス、トランザクション、正規化を、性能と整合性のトレードオフとして判断する。
  • データ不整合や遅いクエリを、実行計画・ロック・件数増加と結びつけて調べる。

手を動かす練習

  • 1つの画面に必要なデータを、どのテーブルから取得するかER図として描く。
  • INSERT/UPDATE/DELETEで失敗した場合に、ロールバックすべき単位を考える。

このレッスンは未完了です。

次のレッスンへ