概要(ACID は「ちゃんとしたトランザクション」の 4 つの約束)
ACID は、データベースのトランザクションが「ちゃんとしている」ことを表す 4 つの性質の頭文字です。
Atomicity(原子性)、Consistency(一貫性)、Isolation(分離性)、Durability(永続性)の 4 つで ACID です。
一言でいうと、
「途中で壊れた状態を残さない」「ルールを守る」「同時に動いても変にならない」「確定したら消えない」
という約束のセットです。
言葉だけだと抽象的なので、ここでは「銀行の振り込み」や「在庫管理」を例にしながら、
一つずつ、初心者目線でかみ砕いていきます。
A:Atomicity(原子性)=「全部やるか、全部やらないか」
送金処理でイメージする原子性
一番イメージしやすいのが Atomicity(原子性)です。
これは、「トランザクションの中の処理は、全部成功するか、全部なかったことになるかのどちらか」という性質です。
銀行の振り込みを考えます。
A さんから B さんに 1000 円送るとき、データベースではだいたい次のような処理になります。
A さんの残高から 1000 円引く。
B さんの残高に 1000 円足す。
この 2 つはセットで意味があります。
もし「A から引くだけ成功して、B に足すところでエラーになった」らどうなるか。
A さんからは 1000 円消えたのに、B さんには届いていない、という「お金が消える」状態になります。
原子性は、これを絶対に許さないための性質です。
トランザクションの中で 2 つの UPDATE を実行し、最後に COMMIT しようとしたとき、
途中でエラーが起きたら、両方とも ROLLBACK されて「最初から何もなかったこと」に戻ります。
ここで重要なのは、「部分的な成功を残さない」ということです。
トランザクションの中の処理は、「運命共同体」になります。
全部通ったら反映、どれか一つでもダメなら全部取り消し。
これが Atomicity です。
Python でのイメージ(atomic ブロック)
Python(特に Django)だと、transaction.atomic() でこの原子性を表現できます。
例えば Django なら、次のようなコードになります。
from django.db import transaction
with transaction.atomic():
a.balance -= 1000
a.save()
b.balance += 1000
b.save()
Pythonこのブロックの中で例外が起きると、
このブロック内の変更は全部 ROLLBACK されます。
「一連の処理をひとまとめにして、全部やるか全部やらないか」にしているわけです。
C:Consistency(一貫性)=「ルールが壊れた状態を確定させない」
「データのルール」が守られている状態とは
Consistency(一貫性)は、「データベースに決めたルールが壊れないようにする」性質です。
ここでいうルールとは、例えば次のようなものです。
残高はマイナスになってはいけない。
メールアドレスは重複してはいけない。
外部キーで参照している先のレコードが必ず存在する。
これらは、データベースの制約(NOT NULL、UNIQUE、CHECK、外部キー制約など)やアプリ側のロジックで定義されます。
一貫性とは、「トランザクションの前後で、これらのルールが守られていること」を意味します。
トランザクションの途中で一時的にルールが崩れても、最終的に COMMIT されるときには、
ルールが守られた状態に戻っている必要があります。
ルールが壊れたらどうなるか
例えば、「残高は 0 以上」という CHECK 制約があるとします。
トランザクションの中で、残高をマイナスにしようとすると、
データベースはエラーを返し、そのトランザクションは ROLLBACK されます。
これにより、「ルールが壊れた状態」が確定されることはありません。
一貫性は、「データベースが持つルールを破る変更は、そもそも確定できない」という性質です。
ここで大事なのは、Consistency は「トランザクションだけの話」ではなく、
制約やアプリのロジックとセットで成り立つ、ということです。
トランザクションは、「ルールを破る変更が途中で紛れ込んでも、最終的に COMMIT されないようにする」役割を担っています。
I:Isolation(分離性)=「同時に動いても、お互いの中途半端な状態を見ない」
同時実行で何が問題になるか
Isolation(分離性)は、複数のトランザクションが同時に動いているときの話です。
現実のアプリでは、ユーザーは一人ではありません。
同時に何人もが操作します。
例えば、次のような状況を考えます。
トランザクション T1:A さんが B さんに 1000 円送金中。
トランザクション T2:B さんが自分の残高を確認中。
T1 の中では、
A から 1000 円引く UPDATE
B に 1000 円足す UPDATE
が順番に行われます。
もし T2 が、「A からは引かれたけど B にはまだ入っていない」という中途半端なタイミングで残高を読んでしまったら、
B さんの残高が「本来とは違う値」に見えてしまいます。
Isolation は、こうした「中途半端な状態」を他のトランザクションから見えにくくする性質です。
「まるで一人ずつ順番に実行されたかのように見せる」
理想的には、複数のトランザクションが同時に動いていても、
外から見ると「どれか一つずつ順番に実行されたように見える」状態が望ましいです。
実際のデータベースでは、
ロックや分離レベル(READ COMMITTED、REPEATABLE READ、SERIALIZABLE など)を使って、
どこまで分離するかを調整します。
初心者のうちは、細かい分離レベルの違いまでは覚えなくて大丈夫です。
まずは、「Isolation は、同時に動いても変な矛盾が起きないようにするための性質」と理解しておけば十分です。
例えば、Django のデフォルト設定では、
多くの RDBMS で READ COMMITTED という分離レベルが使われます。
これは、「他のトランザクションがまだ COMMIT していない変更は見えない」というレベルです。
つまり、「確定していない中途半端な変更」は見えないようになっています。
D:Durability(永続性)=「COMMIT されたら、ちゃんと残る」
COMMIT の「重さ」をイメージする
Durability(永続性)は、「トランザクションが COMMIT されたら、その変更はちゃんと残る」という性質です。
ここでいう「残る」とは、単にメモリ上にあるだけでなく、
ディスクなどの永続ストレージに書き込まれていて、
サーバーが落ちても消えない、という意味です。
例えば、送金処理が終わって COMMIT された直後に、
サーバーがクラッシュしたとします。
再起動したときに、「送金前の状態に戻っていたら」困りますよね。
Durability は、「COMMIT された変更はちゃんと生き残る」ことを保証します。
データベース内部で何が起きているか(ざっくりイメージ)
実際の RDBMS は、
トランザクションログ(WAL:Write-Ahead Log など)に変更内容を書き出し、
それをディスクにフラッシュしてから COMMIT を完了させる、
といった仕組みで Durability を実現しています。
初心者のうちは、内部実装の細かいところまでは気にしなくて大丈夫です。
大事なのは、「COMMIT が成功したら、その変更は“ちゃんとどこかに書かれていて、消えない”」という感覚です。
4 つをまとめて「トランザクションらしさ」として捉える
送金処理に ACID を当てはめてみる
もう一度、A さんから B さんへの送金を例にして、ACID をまとめてみます。
Atomicity(原子性)
A から引く、B に足す、という 2 つの UPDATE は「全部成功するか全部失敗するか」。
途中でエラーが起きたら、どちらもなかったことになる。
Consistency(一貫性)
残高がマイナスにならない、合計金額が合う、などのルールが壊れた状態は COMMIT されない。
制約やロジックと組み合わさって、「おかしなデータ」が確定されない。
Isolation(分離性)
他のトランザクションからは、「送金処理の途中の中途半端な状態」が見えない。
まるで送金処理が一瞬で終わったかのように見える。
Durability(永続性)
COMMIT が成功したら、その送金結果はディスクに書かれていて、サーバーが落ちても消えない。
こうやって見ると、ACID はバラバラの概念ではなく、
「トランザクションをちゃんと動かすための 4 つの側面」だと分かります。
なぜ ACID を意識する必要があるのか
実務でアプリを作るとき、
「トランザクションを使うべきかどうか」
「どこまで分離性が必要か」
「どんな制約を張るか」
といった設計をするときに、
頭の中に ACID のイメージがあるかどうかで、
判断の質が変わります。
例えば、
「ここは途中で失敗したら困るから、atomic で包もう」
「ここは同時実行で競合しそうだから、ロックや分離レベルを考えよう」
「ここは制約を張って、一貫性をデータベース側でも守ろう」
といった発想が自然に出てくるようになります。
Python(Django)で ACID を意識したコードを書くイメージ
Django の transaction.atomic と ACID
Django を例にすると、transaction.atomic() は、
ACID のうち特に Atomicity と Isolation を意識した仕組みです。
例えば、注文処理を Django で書くとします。
from django.db import transaction
def create_order(user, items):
with transaction.atomic():
order = Order.objects.create(user=user)
total = 0
for item in items:
OrderItem.objects.create(
order=order,
product=item.product,
quantity=item.quantity,
price=item.product.price,
)
item.product.stock -= item.quantity
item.product.save()
total += item.product.price * item.quantity
order.total_amount = total
order.save()
Pythonこの中でエラーが起きたら、
Order も OrderItem も stock の更新も、全部 ROLLBACK されます。
これが Atomicity です。
データベース側には、
外部キー制約や NOT NULL、CHECK などを張っておけば、
Consistency も高められます。
Isolation と Durability は、
主にデータベースの設定や実装側の話ですが、
アプリ側は「トランザクションを適切な粒度で使う」ことで、
それらの性質を活かしやすくなります。
まとめ(ACID は「壊れた世界を残さないための 4 本柱」)
ACID を初心者目線でまとめると、こうなります。
Atomicity(原子性)は、「一連の処理は全部成功か全部失敗か。途中の中途半端な成功は残さない」という約束。
Consistency(一貫性)は、「データベースに決めたルール(制約)が壊れた状態を確定させない」という約束。
Isolation(分離性)は、「複数のトランザクションが同時に動いても、お互いの中途半端な状態を見て変な矛盾が起きないようにする」という約束。
Durability(永続性)は、「COMMIT された変更はちゃんとディスクに書かれ、サーバーが落ちても消えない」という約束。
この 4 つがそろっているからこそ、
お金、在庫、ポイント、予約などの「壊れたら困るデータ」を、
安心して RDB に任せられます。
