مقاله

تراکنش دیتابیس برای ثبت سفارش، انتقال موجودی و عملیات چندجدولی — با transaction، deadlock و تست.

Database Transaction در Laravel — DB::transaction و rollback امن

Database Transaction در Laravel — DB::transaction و rollback امن

سفارش ثبت شد اما آیتم‌های سبد پاک نشد — یا برعکس. بدون transaction در محیط concurrent، داده ناسازگار می‌شود. Transaction یعنی «یا همه queryها موفق، یا هیچ‌کدام». در Laravel با DB::transaction() ساده است؛ درک ACID برای debug production ضروری است.

ساده‌ترین شکل

use Illuminate\Support\Facades\DB;

DB::transaction(function () {
    $order = Order::create([...]);
    foreach ($items as $item) {
        $order->items()->create($item);
        Product::where('id', $item['product_id'])->decrement('stock', $item['qty']);
    }
    Cart::where('user_id', auth()->id())->delete();
});

Exception = rollback خودکار.

دستی begin/commit/rollBack

DB::beginTransaction();
try {
    // ...
    DB::commit();
} catch (Throwable $e) {
    DB::rollBack();
    throw $e;
}

ترجیحاً closure transaction — کمتر فراموش rollBack.

return value

$order = DB::transaction(fn () => OrderService::place($dto));

تعداد retry — deadlock

DB::transaction(function () {
    // ...
}, attempts: 5);

MySQL deadlock گاهی retry می‌خواهد — Laravel 8+ پارامتر attempts.

afterCommit — job بعد از commit

DB::afterCommit(function () use ($order) {
    SendOrderConfirmation::dispatch($order);
});

queue قبل از commit = job روی داده commitنشده — خطرناک.

transaction و Eloquent events

created داخل transaction قبل از commit fire می‌شود — listener که به DB بیرون وابسته است از afterCommit استفاده کند.

تست transaction

it('rolls back on failure', function () {
    try {
        DB::transaction(function () {
            Order::factory()->create();
            throw new Exception('fail');
        });
    } catch (Exception) {}
    expect(Order::count())->toBe(0);
});

با RefreshDatabase.

چند connection

transaction per connection — distributed transaction بین دو DB خودکار نیست.

anti-pattern

  • transaction خیلی بلند (lock نگه داشتن)
  • HTTP call خارجی داخل transaction
  • فراموش transaction در transfer پول/موجودی

منطق در Service — نه Controller.

جمع‌بندی

هر عملیات multi-table که باید atomic باشد → transaction. Query Builder و Eloquent هر دو داخل transaction. Polymorphic هم همین rule.

سیستم سفارش