Q1送金の「引き落とし」と「入金」の 2 つの UPDATE を 1 つの単位として、全部成功したら確定し途中で失敗したら全部取り消す性質を何と呼びますか。
トランザクションとロック
この記事は、基礎から複雑なSQL,SQLチューニングまでSQLの実践的なスキルを1からマスターする「SQL入門講座の一部」です。
BEGIN/COMMIT/ROLLBACKの原子性、SAVEPOINTでの部分巻き戻し、AUTOCOMMIT、ロック・分離レベル・ACID特性の考え方を、図と読むだけのコード例で整理します。
本記事は図と読むだけのコードで構成されています
トランザクションは「同時に複数の利用者がアクセスする本物の RDBMS」の上で初めて意味を持つ仕組みで、本講座のブラウザ上のコンソールでは挙動を正しく再現できません。
そのため本記事には実行用の演習を設けず、各セクションのcodeブロックは実機の RDBMS(MySQL / PostgreSQL / Oracle など)で動かす想定の例示として読んでください。
トランザクションとは — 複数の SQL をひとまとまりで扱う
トランザクション(transaction)とは、複数の SQL 文を「ひとまとまり」として扱い、全部成功したらまとめて確定し、途中で失敗したらまとめて取り消す仕組みです。
銀行の送金(引き落としと入金の 2 つのUPDATE)、ネット注文の確定(在庫の減算と注文行の追加)など、「片方だけが成立すると困る」処理で必須になります。
基本の構文はBEGIN;(開始)→ 複数文 → COMMIT;(確定)/ ROLLBACK;(取り消し)です。
本記事のcodeブロックでは、口座を表すaccountテーブルを送金の例として使います。列はaccount_id(口座番号)、owner(名義)、balance(残高)の 3 列です。初期データは以下を想定して読んでください。
| account_id | owner | balance |
|---|---|---|
| 1 | Alice | 1000 |
| 2 | Bob | 500 |
| 3 | Carol | 800 |
| 4 | Dave | 1200 |
原子性 — BEGIN 〜 COMMIT / ROLLBACK
BEGIN;で開始しCOMMIT;で確定すると、その間の変更がすべてまとめて反映されます。
代わりにROLLBACK;を実行すると、BEGIN;以降の変更がすべて取り消され、開始前の状態に戻ります。
「全部反映されるか、1 つも反映されないか」のどちらかにする性質を原子性(atomicity、これ以上分割できないひとまとまりという意味)と呼びます。
CHECK制約違反などのエラーが起きた場合も、ROLLBACK;で安全に巻き戻すのが基本動作です。
-- 送金: Alice -> Bob へ 100 (実機で動かす想定)
BEGIN;
UPDATE account SET balance = balance - 100 WHERE owner = 'Alice';
UPDATE account SET balance = balance + 100 WHERE owner = 'Bob';
COMMIT; -- 2 つの UPDATE が同時に確定
-- 取り消し: 途中でやめる / エラーが出た
BEGIN;
UPDATE account SET balance = balance - 300 WHERE owner = 'Alice';
UPDATE account SET balance = balance + 300 WHERE owner = 'Bob';
ROLLBACK; -- 2 つの UPDATE が両方とも取り消される
SAVEPOINT — 一部だけ巻き戻す
SAVEPOINT 名前;(セーブポイント、途中の保存地点という意味)でトランザクションの途中に印を付けておくと、ROLLBACK TO 名前;でそのセーブポイントまでだけ巻き戻せます。
印より前の変更は残ったままなので、別の操作を続けてから最後にCOMMIT;で確定できます。
たとえば「注文確定→ポイント加算→在庫減算」のような複数ステップで、在庫減算だけ失敗したのでそのステップだけやり直したいような場面で使います。トランザクション全体を捨てるROLLBACK;より細かく制御できます。
-- SAVEPOINT で 2 つ目の更新だけ取り消す (実機で動かす想定)
BEGIN;
UPDATE account SET balance = balance - 100 WHERE owner = 'Carol';
SAVEPOINT sp1;
UPDATE account SET balance = balance - 100 WHERE owner = 'Dave';
ROLLBACK TO sp1; -- Dave だけ取り消す (Carol -100 は残る)
COMMIT; -- Carol -100 のみ確定
AUTOCOMMIT — 既定では文ごとに自動確定される
BEGIN;を書かずに単独でUPDATEやINSERTを実行すると、その1 文ごとに自動で確定されます。
これをAUTOCOMMIT(自動コミット、文単位で自動的に確定される既定モードという意味)と呼びます。
複数文を「まとめて確定/まとめて取り消し」したいときは、必ずBEGIN;で明示的にトランザクションを開始する必要があります。
ロック・デッドロック — 同時実行の考え方
実務のデータベースは複数の利用者が同時にアクセスします。たとえば Alice の残高 1000 円に対して、店 X の決済と店 Y の決済が同時に走ると、両方が「1000 円から引く」と読んでしまい、片方の引き落としが消える危険があります。
これを防ぐのがロック(lock、ある利用者が処理している間、同じ行への他の操作を待たせる仕組み)です。先に処理を始めた側がその行をロックし、もう片方は待たされます。
一方、利用者 A が「口座 1 → 口座 2」の順にロックし、利用者 B が「口座 2 → 口座 1」の順にロックすると、互いが相手の持つロックを待ち合い、どちらも進めないデッドロックになります。対策として「常に同じ順序でロックする(例: account_id昇順)」がよく使われ、デッドロックが起きた場合は DB が片方のトランザクションを強制的に中断します。
分離レベルと ACID — 用語のまとめ
分離レベル(isolation level)は「同時に走る他のトランザクションの途中結果をどこまで見えなくするか」の強さの段階です。
弱い設定(READ COMMITTEDなど)は速い代わりに他の変更が見えやすく、強い設定(SERIALIZABLE)は安全な代わりに待ちが増えます。
原子性(A)・一貫性(C)・分離性(I)・永続性(D、COMMITした結果はディスクに残り、停電やクラッシュ後も失われない性質)の 4 つをまとめて ACID 特性と呼びます。
ロックや分離レベルの細かい挙動は製品ごとに違うため、実務では使う DB の仕様を確認します。
| ACID | 意味 |
|---|---|
| A — 原子性 (Atomicity) | BEGIN〜COMMITの中の文が全部反映されるか、1 つも反映されないか |
| C — 一貫性 (Consistency) | トランザクション前後でCHECK制約・外部キーなどの整合性が保たれる |
| I — 分離性 (Isolation) | 同時に走る他のトランザクションの途中結果を、分離レベルに応じて見えなくする |
| D — 永続性 (Durability) | COMMITした結果はディスクに残り、停電・クラッシュ後も失われない |
理解度チェック
まずは1問ずつ答えてみましょう。
Q2BEGIN; UPDATE ...; UPDATE ...; ROLLBACK;を実行したとき、テーブルはどうなりますか。
Q3BEGIN;を書かずに単独でUPDATEを 1 文だけ実行したときの動作として正しいものはどれですか。