- Laravel
- eloquent
- concurrency
- database
lockForUpdate() only works inside a transaction
A SELECT … FOR UPDATE outside a transaction releases its lock at once, so the check-then-write gap stays open. Wrap the read and the write in DB::transaction().
1 min read
lockForUpdate() asks the database to hold a row lock until the current transaction ends. Without a transaction the query runs in autocommit mode: the lock is taken and released in the same statement, and two requests can still both read a free seat and both claim it.
DB::transaction(function () use ($seatId, $user) {
$seat = Seat::whereKey($seatId)->lockForUpdate()->first();
abort_if($seat->owner_id !== null, 409, 'Seat already taken.');
$seat->update(['owner_id' => $user->id]);
});Inside the transaction the second request waits at the SELECT until the first one commits, then sees the owner and aborts. Keep the transaction short: only the read, the check and the write.