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.