トランザクションの分離レベルと、起きる問題を説明できますか

同時に動くトランザクションどうしが、どこまで互いの途中を見てよいかの段階。ダーティリード、ノンリピータブルリード、ファントムリードを例で説明し、在庫の引き当ては行ロックか楽観ロックで守る、と答えます。

答えてみる

面接官に「トランザクションの分離レベルと、起きる問題を説明できますか」と聞かれたつもりで、答えを見る前に自分の言葉で答えます。

声に出して答えた人は「書かずに答えを見る」で開けます。

解説を読む

聞かれ方から関連するコースまで、この問いの解説を全部ここに置いています。

こう聞かれます

  • 「トランザクションの分離レベルと、起きる問題を説明できますか」
  • 「在庫が 1 個の商品を、2 人が同時に買おうとしました。どう守りますか」
  • 「行ロックと楽観ロックは、どう使い分けますか」

面接官が見ている点

  • 分離レベルを、「同時に動く処理どうしが、互いの途中をどこまで見てよいか」と一言で言えるか
  • 3 つの読み取りの問題を、例で区別できるか
  • DB ごとに既定のレベルと動きが違う、と知っているか (PostgreSQL と MySQL)
  • 在庫のような「読んでから書く」処理を、行ロックか楽観ロックで守れるか

短い答え

  • 分離レベルは、同時に動くトランザクションどうしが、互いの途中の変更をどこまで見てよいかを決める段階で、SQL の標準では Read Uncommitted から Serializable までの 4 つがあります。
  • レベルが低いほど速く動けますが、ダーティリード、ノンリピータブルリード、ファントムリードといった読み取りの食い違いが起きます。
  • 在庫の引き当てのように「読んでから書く」処理は、分離レベルだけに頼らず、行ロック (SELECT ... FOR UPDATE) か、version の列を使う楽観ロックで守ります。

少し詳しい答え

ACID (トランザクションの 4 つの約束) のうち、ここで扱うのは I の分離性 (Isolation) です。

3 つの読み取りの問題

  • ダーティリードほかのトランザクションが、まだコミットしていない書き込みを読んでしまうこと。相手が取り消すと、無かった値を読んだことになります
  • ノンリピータブルリード (ファジーリードとも言います) 同じ行を 2 回読むと、その間にほかのトランザクションがコミットした更新で、値が変わっていること
  • ファントムリード同じ条件で 2 回読むと、その間に行が足されたり消されたりして、条件に合う行の集まりが変わっていること

さらに、1 つずつ順に流したどの順番とも合わない結果になる、直列化異常という問題もあります。

4 つのレベル

SQL の標準では、Read Uncommitted、Read Committed、Repeatable Read、Serializable の順に、防ぐ問題が増えます。Serializable は、1 つずつ順に流したのと同じ結果を約束する、いちばん強いレベルです。

ただし、DB ごとに既定と動きが違います。

  • PostgreSQL 既定は Read Committed です。Read Uncommitted を指定しても Read Committed と同じ動きになり、Repeatable Read でもファントムリードは起きません
  • MySQL (InnoDB) 既定は Repeatable Read です

なので、「この DB の既定はどれで、そのレベルで何が起きるか」を、使う DB の文書で確かめます。

在庫を守る

在庫が 1 個の商品を 2 人が同時に買うと、2 人とも「残り 1」と読んで、2 人とも買えてしまうことがあります。読んでから書くまでの間に、相手の更新が割り込むからです。

1 つ目の手は、読むときに行をロックすることです (悲観ロック)。

SQL クエリ

BEGIN;
SELECT stock FROM items WHERE id = 7 FOR UPDATE;
-- stock が 1 以上なら
UPDATE items SET stock = stock - 1 WHERE id = 7;
COMMIT;

FOR UPDATE を付けると、ほかのトランザクションは、この行をロックしようとするとコミットまで待たされます。

2 つ目の手は、version の列で「読んだときから変わっていないか」を書くときに確かめることです (楽観ロック)。

SQL クエリ

UPDATE items
SET stock = stock - 1, version = version + 1
WHERE id = 7 AND version = 3 AND stock > 0;
-- 更新した行が 0 なら、誰かが先に変えたので、やり直すか断る

トレードオフ

  • 分離レベルを上げる食い違いは減りますが、待ちや、DB による取り消し (直列化の失敗) が増え、やり直しの処理が要ります
  • 行ロック確実ですが、取り合いが多いと待ちが長くなり、ロックの順番によってはデッドロック (互いに相手を待って止まること) も起きます。同時に同じ行を触ることが多い場面に向きます
  • 楽観ロック待たずに済みますが、ぶつかったときにやり直すか断る処理が要ります。ぶつかることが少ない場面に向きます

在庫を減らすだけなら、WHERE stock > 0 を付けた 1 本の UPDATE で、減らせたかを見る、という簡単な手で足りることもあります。

話すときは、自分が関わったサービスで「同時に押されたら困る処理」を 1 つ挙げ、どの手で守ったかを話します。

よくある間違い

  • 3 つの読み取りの問題を混ぜて覚える同じ行の値が変わるのがノンリピータブルリード、行の集まりが変わるのがファントムリード、と分けます
  • どの DB も同じ既定だと思う PostgreSQL は Read Committed、MySQL (InnoDB) は Repeatable Read が既定です
  • トランザクションで囲めば在庫は守れると思う既定のレベルでは、読んでから書く間に割り込まれることがあります。行ロックか楽観ロックを使います

深掘りの問い

  • 「デッドロックを減らすには」複数の行をロックするときは、いつも同じ順番で取る、トランザクションを短くする、DB がデッドロックで取り消したらやり直す、と答えます
  • 「Serializable にすれば全部解決しますか」食い違いは防げますが、取り消しが増えるので、取り消されたらやり直す作りが要る、と答えます
  • 「楽観ロックと悲観ロック、どちらを選びますか」同じ行を同時に触ることが多いなら悲観ロック、少ないなら楽観ロック、と答えます

確認日 2026年10月24日

技術面接問題の一覧へ戻る