ロックとデッドロック

ロックとは

ロックは、複数の処理が同じデータを同時に書き換えて壊すのを防ぐための仕組みです。データベースは更新中の行に鍵をかけ、他の処理を待たせることで整合性を守ります。

たとえば在庫が1個の商品に2人が同時に注文した場合、ロックが無ければ両方が「在庫あり」と判断して二重販売が起きます。ロックはこうした競合を防ぎ、[[トランザクションとACID]] の分離性(Isolation)を支えます。

ロックの種類

種類別名同時に取れるか
共有ロック読み取りロック(S)共有ロック同士は同時に取れる
排他ロック書き込みロック(X)他のロックとは同時に取れない
  • 共有ロック — 読んでいる間、他の人も読めるが書き換えは待たされる
  • 排他ロック — 書き換えている間、他の人は読むことも書くこともできない

またロックの範囲にも段階があり、行ロック(1行だけ)、テーブルロック(表全体)などがあります。範囲が広いほど待ちが増えるため、行単位で細かくロックできる方が同時実行性は高くなります。

デッドロック

デッドロック(deadlock)は、複数のトランザクションが互いの持つロックの解放を待ち合い、永久に進めなくなる状態です。

  1. トランザクションAが行1をロックし、次に行2を要求する
  2. 同時にトランザクションBが行2をロックし、次に行1を要求する
  3. 互いに相手の解放待ちになり、どちらも進めない

多くのデータベースはデッドロックを自動検知し、片方を強制的に失敗させて解消します。アプリ側にはエラーが返るため、リトライ処理を用意しておくのが実務の定石です。

回避のコツ

  • 更新の順序をそろえる — どの処理でも「行1 → 行2」の順にロックを取れば、待ち合いの輪ができない
  • トランザクションを短くする — 中で外部APIを呼ぶ・人の入力を待つといった長時間の処理を含めない
  • 適切な [[データベースインデックス]] を貼る — インデックスが無いと広い範囲にロックがかかり、無関係な行の更新まで待たされる
  • 必要な行だけをロックする — [[SQL]] の条件を絞り、テーブル全体を巻き込まない

初学者向けポイント

  • 「なんとなく遅い」の原因がロック待ちであることは多い — 実行中のトランザクションとロック状況を確認するビューが各データベースに用意されている
  • トランザクションを開けたまま放置すると、他の処理を止め続けるうえ [[コネクションプーリング]] の接続も枯渇させる
  • デッドロックは「起きないようにする」だけでなく「起きても安全に再実行できる」設計にしておく

関連技術とのつながり

  • [[トランザクションとACID]] — ロックは分離性を実現する主要な手段
  • [[リレーショナルデータベース]] — 行ロック・テーブルロックの挙動は製品ごとに差がある
  • [[データベースインデックス]] — ロック範囲を狭め、待ちを減らす効果がある
  • [[SQL]] — 更新文の書き方がロック範囲と順序を決める
  • [[コネクションプーリング]] — 長いロック待ちは接続の枯渇に直結する
  • [[実行計画とクエリチューニング]] — 実行計画がきれいなのに遅いならロック待ちを疑う
  • [[トランザクションログとWAL]] — 長いトランザクションはログの解放も止める
Q: 排他ロック(書き込みロック)の説明として正しいのはどれ?
- [x] 保持している間、他の処理は読むことも書くこともできない
- [ ] 何個でも同時に取得できる
- [ ] 読み取り専用の処理にだけ使われる
解説: 排他ロックは他のロックと同時に取得できません。共有ロックは共有ロック同士なら同時に取れます。

Q: デッドロックとはどのような状態?
- [ ] データベースの容量が不足して書き込めない状態
- [x] 複数のトランザクションが互いのロック解放を待ち合い、どちらも進めない状態
- [ ] インデックスが壊れて検索できない状態
解説: 互いに相手の持つロックを要求し合うと待ちの輪ができ、どちらも永久に進めなくなります。

Q: デッドロックを避ける方法として本文で挙げられているのはどれ?
- [ ] トランザクションの中で外部APIの応答を待つ
- [x] どの処理でも同じ順序でロックを取得する
- [ ] インデックスをすべて削除する
解説: ロック取得の順序をそろえれば待ち合いの輪ができません。トランザクションを短く保つことも有効です。