DEV Community

Cover image for Stop Double-Booking: Optimistic Locking in Laravel
Prajapati Paresh
Prajapati Paresh

Posted on • Originally published at smarttechdevs.in

Stop Double-Booking: Optimistic Locking in Laravel

The Race Condition Catastrophe

In B2B SaaS platforms at Smart Tech Devs, handling limited resources is a high-stakes operation. Imagine an inventory system where you have exactly 1 enterprise server rack left in stock. Two different sales reps load the inventory page at the exact same time. The database tells both of them: "1 in stock."

Rep A clicks "Sell" and Rep B clicks "Sell" at the exact same millisecond. Because both of their PHP processes queried the database a fraction of a second ago and saw stock = 1, both processes execute $item->decrement('stock'). The database allows both writes. You have now sold 2 server racks, but you only had 1. Your stock is now at -1, your supply chain is broken, and a client is furious. To survive high-concurrency environments, you must implement Optimistic Locking.

Pessimistic vs. Optimistic Locking

You could use Pessimistic Locking (lockForUpdate()), which physically locks the database row until the transaction finishes. But if the transaction is slow, it starves your database connection pool.

Optimistic Locking is vastly more scalable. Instead of locking the row, you assume collisions are rare. You add a version integer column to your table. When a user queries the row, they get the data and the version number (e.g., Version 1). When they try to update the row, the query says: "Update the stock to 0, BUT ONLY IF the version is still 1." If Rep A updates it first, the version becomes 2. When Rep B's query arrives a millisecond later looking for Version 1, the database rejects it. Rep B gets a graceful error instead of an oversold product.

Step 1: Architecting the Versioned Migration

First, we add the version tracking column to our critical inventory or financial tables.


use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateInventoryItemsTable extends Migration
{
    public function up(): void
    {
        Schema::create('inventory_items', function (Blueprint $table) {
            $table->id();
            $table->string('sku')->unique();
            $table->integer('stock_count');
            
            // The crucial column for Optimistic Locking
            $table->integer('version')->default(1); 
            
            $table->timestamps();
        });
    }
}

Step 2: Enforcing the Lock in Eloquent

While Laravel doesn't have Optimistic Locking built into the core Eloquent model natively, it is incredibly easy to implement using a Model Trait or directly in your update logic.


namespace App\Services;

use App\Models\InventoryItem;
use Illuminate\Support\Facades\DB;
use Exception;

class CheckoutService
{
    public function purchaseItem(int $itemId, int $quantity)
    {
        // 1. Fetch the item and its CURRENT version
        $item = InventoryItem::findOrFail($itemId);

        if ($item->stock_count < $quantity) {
            throw new Exception("Insufficient stock.");
        }

        // 2. The Optimistic Update
        // We instruct the database to only update the row if the version hasn't changed.
        $affectedRows = DB::table('inventory_items')
            ->where('id', $item->id)
            ->where('version', $item->version) // The Lock Condition
            ->update([
                'stock_count' => $item->stock_count - $quantity,
                'version' => $item->version + 1, // Increment version for the next operation
                'updated_at' => now(),
            ]);

        // 3. Evaluate the result
        if ($affectedRows === 0) {
            // Rep A already updated the row! Rep B's query affected 0 rows.
            \Log::warning("Optimistic lock failure on Item {$itemId}.");
            throw new Exception("This item was modified by another user. Please refresh and try again.");
        }

        return true;
    }
}

The Engineering ROI

By implementing Optimistic Locking, you guarantee absolute mathematical data integrity without sacrificing database read performance. You completely eliminate the possibility of double-booking, inventory overselling, or financial ledger corruption, ensuring your SaaS operates flawlessly even during massive, synchronized traffic spikes.

Top comments (0)