DB排他制御はロック方法より業務上の競合から考える
DB 排他制御設計で迷う場面は、実務ではかなり多いです。楽観ロックで十分なのか。悲観ロックを使うべきか。トランザクション分離レベルで解決できるのか。こうした判断は、技術方式だけでは決まりません。
結論から言うと、DB排他制御では競合頻度、業務上の許容、ロック時間、再試行できるかを分けて考えます。特に在庫、予約、承認、ポイント、請求のような更新処理では、排他制御の設計ミスが直接障害になります。
例えば、同じ在庫を2人が同時に購入する。承認済みの申請を別ユーザーが更新する。集計済みの請求データを再計算する。こうしたケースでは、単に@Transactionalを付けるだけでは足りません。
そこでこの記事では、Java/Spring BootやSQLを扱う実務エンジニア向けに、DB排他制御設計の判断基準を整理します。楽観ロック、悲観ロック、分離レベル、デッドロック、レビュー観点まで扱います。
DB排他制御で防ぎたい問題
まず、排他制御で何を防ぎたいのかを明確にします。ロックを使うこと自体が目的ではありません。業務上許容できないデータ不整合を防ぐことが目的です。
| 問題 | 例 | 影響 |
|---|---|---|
| lost update | 後から保存した更新が前の更新を上書きする | ユーザーの変更が消える |
| 二重処理 | 同じ注文を2回確定する | 請求や在庫がずれる |
| 在庫超過 | 残数1の商品を2人が購入する | 業務補正が必要になる |
| 状態不整合 | 承認済みデータを差し戻し前提で更新する | ワークフローが壊れる |
| デッドロック | 複数行を異なる順序でロックする | 処理失敗や再試行が必要になる |
そのため、レビューでは「どのロックを使ったか」だけではなく、「どの不整合を防ぐ設計か」を確認します。ここが説明できない排他制御は、過剰か不足のどちらかになりがちです。
楽観ロックは競合が少ない更新に向いている
楽観ロックは、更新時にversionやupdated_atを確認し、他の更新が入っていたら失敗させる方式です。読み取り時にはロックを待たせません。そのため、競合頻度が低い業務に向いています。
UPDATE orders
SET status = 'APPROVED',
version = version + 1
WHERE id = :id
AND version = :version;
更新件数が0件なら、他のユーザーが先に更新したと判断できます。この場合、APIでは409 Conflictを返し、画面側で再読み込みや差分確認を促します。
一方で、楽観ロックは競合を防ぐのではなく、競合を検知して失敗させる方式です。競合が頻繁に起きる業務では、ユーザーが何度もやり直すことになります。そのため、UXと業務上の許容を確認します。
悲観ロックは待たせても守りたい処理に使う
悲観ロックは、対象行を先にロックして、他の処理を待たせる方式です。PostgreSQLではExplicit Lockingとして行ロックやテーブルロックが説明されています。MySQLでもInnoDB Locking and Transaction Modelでロックの考え方を確認できます。
SELECT id, stock
FROM products
WHERE id = :productId
FOR UPDATE;
例えば、在庫引当や座席予約のように、同時更新をできるだけ直列化したい処理で候補になります。先にロックした処理が完了するまで、後続処理は待ちます。
ただし、悲観ロックは性能と待ち時間に影響します。ロック範囲が広い。トランザクションが長い。外部API呼び出し中にロックを保持する。こうした設計は障害につながります。そのため、ロック時間を短くする設計が必須です。
トランザクション分離レベルだけに頼らない
DBの分離レベルは、同時実行時にどの現象を許すかを決めます。PostgreSQLのTransaction Isolationでも、Read CommittedやRepeatable Readなどが整理されています。
ただし、分離レベルを上げればすべて解決するわけではありません。分離レベルを上げると、待ち時間やリトライ、デッドロックの扱いが変わります。また、DB製品によって挙動が異なる部分もあります。
そのため、アプリケーション側の業務チェック、versionによる競合検知、必要箇所の行ロックを組み合わせます。特にSpring Bootでは、@Transactionalの境界とロック取得の順序をセットでレビューします。
DB排他制御設計の判断基準
実務では、楽観ロックと悲観ロックのどちらが常に正しいとは言えません。次の観点で判断します。
| 観点 | 楽観ロックが向く | 悲観ロックが向く |
|---|---|---|
| 競合頻度 | 低い | 高い |
| ユーザー体験 | 失敗後の再読み込みを許容できる | 待たせても確実に処理したい |
| 処理時間 | 長めでもロック待ちは少ない | 短いトランザクションに限定する |
| 業務影響 | 競合時に再操作できる | 二重処理や超過が致命的 |
| 実装負荷 | version管理と競合時表示が必要 | ロック順序とタイムアウト設計が必要 |
例えば、プロフィール編集や管理画面のマスタ更新は楽観ロックで十分なことが多いです。一方で、在庫引当、座席確保、決済確定のような処理では悲観ロックや一意制約を組み合わせます。
さらに、DB制約も排他制御の一部として扱います。アプリケーションだけで二重登録を防ぐより、ユニーク制約で最後の防衛線を作るほうが安全です。ただし、制約違反時のエラーハンドリングも設計します。
デッドロックは設計と運用の両方で見る
デッドロックは、複数のトランザクションが互いのロック解除を待つ状態です。完全にゼロにするのは難しいですが、発生しにくい設計にはできます。
- 複数行を更新する場合はロック取得順序を統一する
- トランザクション内で外部APIを呼ばない
- ユーザー入力待ちの間にロックを保持しない
- 更新対象を必要最小限に絞る
- タイムアウトとリトライ方針を決める
例えば、注文ヘッダと注文明細を更新する処理が複数あるなら、必ず同じ順序でロックします。ある処理はヘッダから、別の処理は明細からロックする設計は危険です。
また、デッドロックが発生した場合に自動リトライできるかも業務次第です。冪等性がある処理なら再試行できます。一方で、外部送信や決済が絡む処理では、単純なリトライが二重処理を招くことがあります。
排他制御でよくある失敗例
ここでは、実務レビューで見つかりやすい失敗例を整理します。排他制御の失敗は、単体テストだけでは再現しにくい点が厄介です。
| 失敗例 | 起こる問題 | 改善案 |
|---|---|---|
| @Transactionalを付けただけ | lost updateを防げない | versionや行ロックを設計する |
| ロック範囲が広い | 待ち時間が増える | 対象行と処理時間を絞る |
| 外部API中にロック保持 | 障害時にロックが長引く | DB更新と外部連携を分ける |
| 競合時の画面設計がない | ユーザーが何をすべきか分からない | 409時の再読み込み導線を作る |
| DB制約に頼りきる | エラーが不親切になる | 事前チェックと制約違反処理を併用する |
特に多いのは、トランザクション境界が広すぎる設計です。メール送信、ファイル出力、外部API呼び出しまで同じトランザクションに入れると、DBロックが長時間保持されます。その結果、待ち時間やデッドロックの原因になります。
レビューで見るべきDB排他制御の観点
最後に、設計レビューで確認したい観点をまとめます。実装済みコードだけでなく、業務フローや画面仕様と一緒に確認します。
- 同時更新で防ぎたい不整合が明確か
- 楽観ロックと悲観ロックの選択理由を説明できるか
- トランザクション境界が必要最小限か
- ロック取得順序が統一されているか
- 競合時にユーザーへ何を表示するか決まっているか
- デッドロックやタイムアウト時のリトライ方針があるか
- ユニーク制約や外部キー制約を最後の防衛線として使っているか
一方で、排他制御を強くしすぎると性能が落ちます。すべて悲観ロックにする設計は、ピーク時に詰まりやすくなります。そのため、守るべき整合性と許容できる失敗を分けて判断します。
よくある質問
楽観ロックと悲観ロックはどちらを先に検討すべきですか?
競合頻度が低く、失敗時に再操作できるなら楽観ロックから検討します。一方で、在庫や予約のように待たせても整合性を守りたい処理では悲観ロックが候補です。まず業務影響を確認します。
トランザクション分離レベルを上げれば排他制御は不要ですか?
不要とは言えません。分離レベルは同時実行時の見え方を制御しますが、業務上の競合をどう扱うかは別問題です。例えば、競合時に失敗させるのか、待たせるのか、再試行するのかはアプリケーション設計で決めます。
排他制御のテストはどう考えるべきですか?
単体テストだけでは不十分です。同時実行テスト、DB制約違反時のテスト、デッドロックやタイムアウト時の挙動確認が必要です。また、APIとして409や500をどう返すかまで確認します。
まとめ
DB排他制御設計では、楽観ロックと悲観ロックの名前を知っているだけでは足りません。どの不整合を防ぎたいのか、競合時に待たせるのか失敗させるのか、再試行できるのかを分けて判断します。
また、ロックは強くするほど安全に見えますが、性能や待ち時間に影響します。一方で、弱すぎる排他制御はデータ不整合を招きます。そのため、業務影響と運用負荷のバランスを設計レビューで確認することが重要です。
Java/Spring BootやDB設計の案件では、トランザクション、排他制御、APIエラーハンドリングまで説明できる経験が評価されます。設計レビューでどこまで語れるか、また今後どんな案件を選ぶべきかを相談したい方は、現在の経験を整理するところから話せます。
一度カジュアル面談をしませんか?
株式会社bluenaは「高還元」と「伴走支援」を両立したSES企業です。単価の81〜86%を還元する報酬体系と、専任サポーターによる隔週1on1で、エンジニアが納得できるキャリアを実現します。
まとまっていなくてもOK——まずは現在地を聞かせてください。
カジュアル面談ですので、お気軽にお聞かせください。





