CÓMO MANEJAR TRANSACCIONES EN LARAVEL

Una transacción es un grupo de operaciones SQL que se ejecutan como una unidad atómica:
  • Todo o nada (Atomicity).
  • Son consistentes (Consistency).
  • Aisladas entre sí (Isolation).
  • Duraderas una vez confirmadas.
Para manejarlas de forma elegante hay que usar el facade DB de Laravel: Ejemplo:

use Illuminate\Support\Facades\DB;

DB::transaction(function () {
    $order = Order::create([
        'user_id' => $user->id,
        'total'   => $request->total,
        'status'  => 'pending',
    ]);

    foreach ($request->products as $item) {
        OrderItem::create([
            'order_id' => $order->id,
            'product_id' => $item['id'],
            'quantity'   => $item['qty'],
            'price'      => $item['price'],
        ]);

        // Reducir stock (¡crítico!)
        Product::where('id', $item['id'])->decrement('stock', $item['qty']);
    }

});
Ventajas del ejemplo anterior:
  • Si cualquier línea falla se realiza rollback automático.
  • Código limpio y legible.
  • Laravel maneja el commit y rollback por ti.
Ejemplo:

// Cuando necesitas controlar el rollback manualmente
use Illuminate\Support\Facades\DB;

try {
    DB::beginTransaction();

    $userId = DB::table('users')->insertGetId([
        'name' => 'Ana',
        'email' => 'ana@example.com'
    ]);

    DB::table('profiles')->insert([
        'user_id' => $userId,
        'bio' => 'Diseñadora'
    ]);

    DB::commit();
} catch (\Exception $e) {
    DB::rollBack();

    // Manejar error
    echo "Error: " . $e->getMessage();
}

Mejores prácticas

  • Transacciones cortas: Nada de llamadas HTTP, emails, resize de imágenes o jobs pesados dentro de la transacción:

    $order = null;
    DB::transaction(function () use (&$order) {
        $order = Order::create([...]);
        // solo DB
    });
    
    // Fuera de la transacción
    ProcessPaymentJob::dispatch($order);
    
  • Evitar transacciones muy largas ya que se producen bloqueos y timeouts en las bases de datos.
  • Usa pessimistic locking cuando sea necesario:

    DB::transaction(function () {
        $product = Product::lockForUpdate()->find($id);
        // ... ahora nadie más puede modificar este registro
    });
    
  • Devoler valores desde DB::transaction()

    $newOrder = DB::transaction(function () {
        $order = Order::create([...]);
        // ...
        return $order;
    });
    
  • No abusar ya que un un solo INSERT/UPDATE no necesita transacción ya es atómico.
El pessimistic locking es una técnica usada en bases de datos y sistemas concurrentes para evitar conflictos cuando varios usuarios o procesos intentan acceder o modificar los mismos datos al mismo tiempo.