LaravelLaravel21 min read

Prevent Race Conditions with Row Locking

Avoid overselling inventory and double-updates using transactions and lockForUpdate().

Brandon Hayes
October 18, 2025
2.4k114

Race conditions happen when two requests try to update the same record at the same time.

    Example: two customers buy the last item.
    - Both requests read stock=1
    - Both reduce stock to 0
    - Result: you oversold
    
    ## Safe pattern: lock the row
    
    ```php
    use Illuminate\Support\Facades\DB;
    
    DB::transaction(function () use ($productId) {
      $product = DB::table('products')
        ->where('id', $productId)
        ->lockForUpdate()
        ->first();
    
      if ($product->stock <= 0) {
        throw new Exception('Out of stock');
      }
    
      DB::table('products')
        ->where('id', $productId)
        ->update(['stock' => $product->stock - 1]);
    });
    ```
    
    ## Sequence: how locking prevents conflicts
    
    ```mermaid
    sequenceDiagram
      participant A as Request A
      participant DB as Database
      participant B as Request B
    
      A->>DB: lockForUpdate(product)
      DB-->>A: row locked, stock=1
      B->>DB: lockForUpdate(product)
      DB-->>B: waits (locked)
      A->>DB: update stock=0
      A->>DB: commit (unlock)
      DB-->>B: lock granted, stock=0
    ```
    
    In the next tutorial, we will learn indexing to speed up queries.
#Laravel#Database#Performance#Advanced