Skip to content
OpenCloseEsc
Tips
  • 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.