| この記事の要点 |
|
悲観ロックとは
同じデータを複数のリクエストが同時に更新すると、「在庫 1 個の商品が 2 人に売れる」「残高の加算が片方消える」といった不整合が起きます。悲観ロックは「衝突は起きるもの」と考え、読み取った時点で DB の行にロックをかけ、処理が終わるまで他のトランザクションを待たせる方式です。対になる楽観ロックは、ロックをかけずに更新時にバージョン番号などで衝突を検出します。
| 項目 | 悲観ロック | 楽観ロック |
|---|---|---|
| 仕組み | SELECT 時に DB の行ロックを取得 | UPDATE 時に version 列などで変更の有無を確認 |
| 衝突時 | 後から来た処理が待たされる | 後から来た処理が失敗し、再試行やエラー表示が必要 |
| 向いている場面 | 在庫・残高・採番など衝突が多く、失敗させたくない処理 | 編集画面など衝突が少なく、ロック待ちを避けたい処理 |
共有ロック(参照時にかけるロック)
DB::table('users')->where('votes', '>', 100)->sharedLock()->get();
ロックした行は、他のトランザクションから読むことはできますが、更新・削除や占有ロックはトランザクション終了まで待たされます。「この行を参照している間は内容を変えられたくない」場合に使います(例: 親レコードが存在することを確認してから子レコードを登録する)。
占有ロック(更新時にかけるロック)
DB::table('users')->where('votes', '>', 100)->lockForUpdate()->get();
ロックした行は、他のトランザクションからの更新・削除・共有ロック・占有ロックがすべて待たされます。「読み取った値をもとに更新する」処理では、こちらを使います。なお、ロックをかけない普通の SELECT(MySQL InnoDB の一貫性読み取り)はブロックされません。
実践例:在庫を減らす処理
ロックはトランザクションが終わる(コミットまたはロールバック)まで保持されます。Laravel では DB::transaction() のクロージャ内で使うのが基本です。
use Illuminate\Support\Facades\DB;
use App\Models\Product;
DB::transaction(function () use ($productId, $qty) {
// 1. 行を占有ロックして最新の在庫を読む(他の同じ処理はここで待たされる)
$product = Product::where('id', $productId)->lockForUpdate()->firstOrFail();
// 2. 読んだ値で判定
if ($product->stock < $qty) {
throw new \RuntimeException('在庫不足'); // 例外でロールバックされ、ロックも解放
}
// 3. 更新。クロージャを抜けるとコミットされロックが外れる
$product->decrement('stock', $qty);
}, 3); // デッドロックが起きたら最大 3 回まで実行し直す
Eloquent モデルのクエリでも、クエリビルダと同じく lockForUpdate() / sharedLock() を付けられます。
発行される SQL
| メソッド | MySQL / MariaDB | PostgreSQL |
|---|---|---|
lockForUpdate() | FOR UPDATE | FOR UPDATE |
sharedLock() | LOCK IN SHARE MODE | FOR SHARE |
lock('for update skip locked') | 文字列をそのまま付与。ロック中の行を飛ばして取得(MySQL 8.0 以降・PostgreSQL。ジョブキューの取り出しなどで使う) | |
よくある落とし穴
- トランザクションの外で使う: 自動コミットの状態では SELECT 文が終わった瞬間にロックが外れる。エラーにはならないので気付きにくい
- インデックスのない条件: InnoDB は検索で走査した行(インデックスのギャップを含む)にロックをかける。条件の列にインデックスがないと、テーブルの広い範囲がロックされ、関係ない処理まで待たされる
- デッドロック: 処理 A が行 1→行 2、処理 B が行 2→行 1 の順でロックすると互いに待ち続け、DB が片方をエラーにする。ロックを取る順番を全処理でそろえる(ID の昇順など)、
DB::transactionの再試行回数を指定する - ロック待ちタイムアウト: MySQL は
innodb_lock_wait_timeout(既定 50 秒)を超えるとLock wait timeout exceededエラー。トランザクション内で外部 API 呼び出しなど時間のかかる処理をしない - SQLite では効かない: SQLite 用の文法ではロック句が付かないため、テストを SQLite で動かしていると同時実行の問題を再現できない
- 複数の DB 接続:
DB::connection('other')のクエリは、既定接続のDB::transactionの対象外。DB::connection('other')->transaction(...)を使う
確認方法
- 発行される SQL は
->lockForUpdate()->toSql()で確認できる(末尾にfor updateが付く) - 実際に効いているかは、2 つの端末で
php artisan tinkerを開き、片方でトランザクションを開始してロックしたまま止め、もう片方で同じ行をロックしようとして待たされることを確かめる - MySQL 8.0 では
performance_schema.data_locksやSHOW ENGINE INNODB STATUSで、現在のロックと直近のデッドロックの内容を確認できる
関連
- SQL UPDATE 文の完全ガイド(楽観ロック)
- SQL ROLLBACK 完全ガイド(SAVEPOINT / Spring・Laravel)
- MySQL の実行中 SQL 確認とプロセスキル完全ガイド
- 【Spring】@Transactional アノテーションとは
子ページ
子ページはありません
同階層のページ
人気ページ
- 1 Eclipseで「サーバーに追加または除去できるリソースがありません。」の原因と対処法
- 2 tomcat の起動 / 停止ログと catalina.log・catalina.out の違い
- 3 JavaScript で base URL を取得する方法|window.location.origin
- 4 YouTube Data API v3 エラー一覧|403・400・404 の原因と対処
- 5 Laravel エラー一覧|500/Blade/DB 接続/ルーティングの代表エラー
- 6 3Dグラフィックスのソフト一覧|Blender・Metasequoiaと用途別の選び方
- 7 Spring Frameworkのアノテーション一覧
- 8 【Spring】@Valueアノテーションとは
- 9 CATALINA_HOME の確認方法 (Linux / Mac)
- 10 【Spring】@Autowiredアノテーションとは
最近更新/作成されたページ
- Visual Studio「デザイナーを表示できません」エラーの原因と対処 2026-10-28 12:06:00
- SSH 認証に失敗しました。再試行してください の対処 2026-10-28 12:06:00
- SSH2 秘密鍵の読み込みに失敗しました の原因と対処 2026-10-28 12:06:00
- TeraTerm のよくあるエラーと対処法完全ガイド — Connection refused / 2026-10-28 12:06:00
- Visual Studio エラー一覧|コンパイルエラー・警告の確認方法と対処 2026-10-28 11:39:57
- Whitelabel Error Page の原因と消し方|Spring Boot のエラーページ 2026-10-28 11:39:57
- Eclipseで「サーバーに追加または除去できるリソースがありません。」の原因と対処法 2026-10-28 11:39:57
- SQL WHERE 句完全ガイド 2026-10-28 11:39:57
- SQL JOIN 完全ガイド (INNER/LEFT/RIGHT/FULL OUTER) 2026-10-28 11:39:57
- tomcat の起動 / 停止ログと catalina.log・catalina.out の違い 2026-10-28 10:24:23
- Tomcat の起動確認方法 5 つ|localhost:8080・ポート・プロセス・ログ 2026-10-28 10:24:23
- Oracle 19c COMMITの使い方: 暗黙コミット・自動確定・応答不明時の確認 2026-10-28 09:59:09
- Oracle 19c DELETE文|削除対象の確認・COMMIT前の判断・ROLLBACKの条件 2026-10-28 09:59:09
- Oracle 19c INSERT文|INSERT ALL・SELECT・RETURNINGとMERGEの使い分け 2026-10-28 09:59:09
- Oracle 19cのトランザクション制御|COMMIT・ROLLBACK・分離レベルと接続設定 2026-10-28 09:59:09