Laravel & Backend6 min read

Database Transactions in Laravel: Preventing Data Inconsistency

Md Shahed Alam
Md Shahed Alam
March 15, 2025
Database Transactions in Laravel: Preventing Data Inconsistency

Think about what happens when a customer places an order on your e-commerce site. Your code needs to do several things: create the order record, deduct items from inventory, charge the payment, and send a confirmation email.

Now imagine the payment charge succeeds but then your server crashes before the inventory is updated. You have charged the customer but your stock count is wrong. That is a data consistency problem.

Database transactions solve this. Either all the operations succeed together, or none of them do.

The basic idea

A transaction wraps multiple database operations into one unit. If anything goes wrong inside the transaction, everything is rolled back — as if none of it happened.

php
DB::transaction(function () use ($cart, $user) {
    $order = Order::create([
        'user_id' => $user->id,
        'total' => $cart->total(),
        'status' => 'pending',
    ]);
    
    foreach ($cart->items as $item) {
        $order->items()->create([
            'product_id' => $item->product_id,
            'quantity' => $item->quantity,
            'price' => $item->price,
        ]);
        
        // Deduct from inventory
        $item->product->decrement('stock', $item->quantity);
    }
    
    $this->paymentService->charge($user, $order->total);
});

If $this->paymentService->charge() throws an exception, Laravel automatically rolls back the order creation and the inventory changes. The database is left exactly as it was before.

When you need more control

Sometimes you want to handle the rollback yourself:

php
DB::beginTransaction();

try {
    $order = Order::create($orderData);
    $this->processPayment($order);
    $this->updateInventory($order);
    
    DB::commit();
    return $order;
} catch (PaymentFailedException $e) {
    DB::rollBack();
    // Tell the user their payment failed
    throw $e;
} catch (\Exception $e) {
    DB::rollBack();
    // Log unexpected errors
    Log::error('Order creation failed', ['error' => $e->getMessage()]);
    throw $e;
}

A common mistake

Transactions only cover database operations. If you send an email inside a transaction and then the transaction rolls back, the email has already been sent. You cannot un-send it.

For this reason, keep side effects like emails and external API calls outside the transaction, or use queued jobs that only run after the transaction commits:

php
DB::transaction(function () use ($user) {
    $order = Order::create(...);
    
    // Queue the email — it will only run after the transaction commits
    dispatch(new SendOrderConfirmation($order))->afterCommit();
});

The afterCommit() method is a Laravel feature that holds the job until the transaction successfully commits. Very useful.

When to use transactions

Use them whenever you have two or more related database writes that must all succeed or all fail. Creating a user and their profile. Transferring money between accounts. Processing an order. These are all classic transaction scenarios.

The performance cost is minimal. The protection they provide is significant.

LaravelDatabaseTransactionsData Integrity

Ready to build something great?

Let's talk about your project. We will give you honest advice, a clear plan, and a fair price. No pressure, no sales pitch.

Free consultation
No commitment required
Response within 24 hours