ロックとデッドロック
ロックとは
ロックは、複数の処理が同じデータを同時に書き換えて壊すのを防ぐための仕組みです。データベースは更新中の行に鍵をかけ、他の処理を待たせることで整合性を守ります。
たとえば在庫が1個の商品に2人が同時に注文した場合、ロックが無ければ両方が「在庫あり」と判断して二重販売が起きます。ロックはこうした競合を防ぎ、[[トランザクションとACID]] の分離性(Isolation)を支えます。
ロックの種類
| 種類 | 別名 | 同時に取れるか |
|---|---|---|
| 共有ロック | 読み取りロック(S) | 共有ロック同士は同時に取れる |
| 排他ロック | 書き込みロック(X) | 他のロックとは同時に取れない |
- 共有ロック — 読んでいる間、他の人も読めるが書き換えは待たされる
- 排他ロック — 書き換えている間、他の人は読むことも書くこともできない
またロックの範囲にも段階があり、行ロック(1行だけ)、テーブルロック(表全体)などがあります。範囲が広いほど待ちが増えるため、行単位で細かくロックできる方が同時実行性は高くなります。
デッドロック
デッドロック(deadlock)は、複数のトランザクションが互いの持つロックの解放を待ち合い、永久に進めなくなる状態です。
- トランザクションAが行1をロックし、次に行2を要求する
- 同時にトランザクションBが行2をロックし、次に行1を要求する
- 互いに相手の解放待ちになり、どちらも進めない
多くのデータベースはデッドロックを自動検知し、片方を強制的に失敗させて解消します。アプリ側にはエラーが返るため、リトライ処理を用意しておくのが実務の定石です。
回避のコツ
- 更新の順序をそろえる — どの処理でも「行1 → 行2」の順にロックを取れば、待ち合いの輪ができない
- トランザクションを短くする — 中で外部APIを呼ぶ・人の入力を待つといった長時間の処理を含めない
- 適切な [[データベースインデックス]] を貼る — インデックスが無いと広い範囲にロックがかかり、無関係な行の更新まで待たされる
- 必要な行だけをロックする — [[SQL]] の条件を絞り、テーブル全体を巻き込まない
初学者向けポイント
- 「なんとなく遅い」の原因がロック待ちであることは多い — 実行中のトランザクションとロック状況を確認するビューが各データベースに用意されている
- トランザクションを開けたまま放置すると、他の処理を止め続けるうえ [[コネクションプーリング]] の接続も枯渇させる
- デッドロックは「起きないようにする」だけでなく「起きても安全に再実行できる」設計にしておく
関連技術とのつながり
- [[トランザクションとACID]] — ロックは分離性を実現する主要な手段
- [[リレーショナルデータベース]] — 行ロック・テーブルロックの挙動は製品ごとに差がある
- [[データベースインデックス]] — ロック範囲を狭め、待ちを減らす効果がある
- [[SQL]] — 更新文の書き方がロック範囲と順序を決める
- [[コネクションプーリング]] — 長いロック待ちは接続の枯渇に直結する
- [[実行計画とクエリチューニング]] — 実行計画がきれいなのに遅いならロック待ちを疑う
- [[トランザクションログとWAL]] — 長いトランザクションはログの解放も止める
Q: 排他ロック(書き込みロック)の説明として正しいのはどれ?
- [x] 保持している間、他の処理は読むことも書くこともできない
- [ ] 何個でも同時に取得できる
- [ ] 読み取り専用の処理にだけ使われる
解説: 排他ロックは他のロックと同時に取得できません。共有ロックは共有ロック同士なら同時に取れます。
Q: デッドロックとはどのような状態?
- [ ] データベースの容量が不足して書き込めない状態
- [x] 複数のトランザクションが互いのロック解放を待ち合い、どちらも進めない状態
- [ ] インデックスが壊れて検索できない状態
解説: 互いに相手の持つロックを要求し合うと待ちの輪ができ、どちらも永久に進めなくなります。
Q: デッドロックを避ける方法として本文で挙げられているのはどれ?
- [ ] トランザクションの中で外部APIの応答を待つ
- [x] どの処理でも同じ順序でロックを取得する
- [ ] インデックスをすべて削除する
解説: ロック取得の順序をそろえれば待ち合いの輪ができません。トランザクションを短く保つことも有効です。