Operaciones transaccionales en la base de datos en Laravel

Video thumbnail

Dependiendo del tipo de aplicación que queramos construir, muchas veces es necesario realizar múltiples operaciones en la base de datos de manera segura, y no solo aplica a las relaciones de tipo muchos a muchos. Por ejemplo, en el caso de la compra de un libro, los pasos podrían ser los siguientes:

  1. Registrar el identificador de pago en la base de datos.
  2. Descontar las cantidades del inventario.
  3. Registrar el producto en algún listado de compras del cliente.

Estos son solo algunos posibles pasos; pueden existir más, y no necesariamente tienen que ejecutarse en un orden específico. Existen muchos escenarios donde se requieren varias operaciones consecutivas, y si alguna de ellas falla, todas las operaciones anteriores deberían revertirse para evitar dejar el proceso en un estado intermedio.

¿Qué son las operaciones transaccionales en Laravel?

Las operaciones transaccionales permiten agrupar varias consultas a la base de datos dentro de una única unidad de trabajo. Todas se ejecutan correctamente o ninguna lo hace.

En términos prácticos:

  • Si todo sale bien → commit
  • Si algo falla → rollback

Esto garantiza las propiedades ACID:

  • Atomicidad: todo o nada.
  • Consistencia: la base de datos nunca queda en un estado inválido.
  • Aislamiento: otras operaciones no ven estados intermedios.
  • Durabilidad: una vez confirmado, el cambio persiste.

El problema real de las operaciones múltiples

Un ejemplo muy común (y que he vivido varias veces) es una compra:

  • Registrar el pago.
  • Descontar stock.
  • Asociar el producto al usuario.

Si el stock falla en el paso 2 y el pago ya quedó registrado, la orden entra en un estado intermedio. Ese “limbo” es justo lo que las transacciones evitan.

Por ejemplo, si durante el paso dos descubrimos que ya no hay stock disponible, la operación fallaría. Como consecuencia, no se podría completar el paso tres, dejando la orden en un limbo, ya que el paso uno sí se habría ejecutado correctamente.

Por qué las transacciones son clave en aplicaciones reales

Estas permiten agrupar un conjunto de consultas en una única unidad de trabajo, asegurando que todas se ejecuten correctamente o ninguna de ellas lo haga.

En otras palabras:

  • Si una consulta falla, todas las modificaciones realizadas previamente en la base de datos se revierten.
  • Esto garantiza la integridad de los datos, evitando que una orden quede incompleta o en un estado inconsistente.
  • Se asegura la atomicidad de las operaciones, ya que todas se ejecutan como una sola unidad de trabajo.

En nuestro ejemplo de compra de libro, si ocurre algún problema en cualquiera de los pasos, Laravel revertirá todas las operaciones automáticamente. De esta forma, la aplicación se mantiene segura y confiable, evitando problemas como inconsistencias en inventario, registros duplicados o transacciones incompletas.

Sin transacciones:

  • Órdenes incompletas
  • Inventarios negativos
  • Registros huérfanos
  • Estados imposibles de depurar

Con transacciones:

  • El sistema se protege solo.
  • El código es más predecible.
  • Los errores son controlables.

Cómo implementar operaciones transaccionales en Laravel

Laravel ofrece dos formas principales de trabajar con transacciones.

Usando DB::transaction() (forma recomendada)

Es la opción más limpia cuando las operaciones son claras y no necesitas lógica extra en el rollback.

use Illuminate\Support\Facades\DB;

try {
    // inicia la transaccion
    DB::beginTransaction();

    // Realiza tus consultas aquí
    DB::table('users')->insert(['name' => 'John Doe']);
    DB::table('orders')->insert(['user_id' => 1, 'total_amount' => 100]);

    DB::commit(); // Confirma los cambios en la base de datos
} catch (\Exception $e) {
    DB::rollback(); // Revierte los cambios en caso de error
    return $e->getMessage();
}

En este ejemplo:

  • DB::beginTransaction() inicializa la transacción.
  • Dentro del bloque try, realizamos nuestras todas las operaciones a la base de datos mediante nuestros modelos o similares.
  • Si todas las operaciones que queríamos realizar fueron resueltas exitosamente, llamamos al método DB::commit() para confirmar los cambios.
  • Si ocurre un error, el bloque catch llama a DB::rollback() para revertir todos los cambios que se hayan podido realizar.

También se puede implementar mediante un callback:

DB::transaction(function () {

    // Realiza tus consultas aquí
    DB::table('users')->insert(['name' => 'John Doe']);
    DB::table('orders')->insert(['user_id' => 1, 'total_amount' => 100]);
    DB::commit();
});

Otro ejemplo:

use Illuminate\Support\Facades\DB;

DB::transaction(function () {
    $order = Order::create([
        'user_id' => auth()->id(),
        'total' => 100
    ]);

    foreach ($items as $item) {
        $product = Product::findOrFail($item['id']);
        $product->decrement('stock', $item['quantity']);

        if ($product->stock < 0) {
            throw new Exception('Stock insuficiente');
        }

        $order->items()->create([
            'product_id' => $product->id,
            'quantity' => $item['quantity']
        ]);
    }
});

Si ocurre cualquier excepción:

  • Laravel hace rollback automáticamente.
  • No necesitas try/catch manual.

Manejo manual con beginTransaction, commit y rollback

Cuando necesito control total (logs, manejo personalizado de errores o notificaciones), prefiero esta opción.

use Illuminate\Support\Facades\DB;
DB::beginTransaction();
try {
   $user = User::create($data);
   $user->syncRoles([$data['role']]);
   // otras operaciones críticas...
   DB::commit();
   return $user;
} catch (\Exception $e) {
   DB::rollback();
   throw $e;
}

Este enfoque me ha resultado útil cuando debo registrar errores específicos antes de revertir los cambios.

Transacciones usando modelos Eloquent

Una ventaja clave es que las transacciones funcionan perfectamente con relaciones Eloquent:

DB::transaction(function () {
   $user = User::create([
       'name' => 'John Doe',
       'email' => 'john@example.com',
   ]);
   $user->profile()->create([
       'bio' => 'Perfil creado dentro de la transacción',
   ]);
});

Si el perfil falla, el usuario no se guarda.

Casos de uso comunes de operaciones transaccionales

Creación de órdenes y productos

  • Orden
  • Detalles
  • Descuento de inventario

Pagos e inventario

En más de una ocasión detecté stock negativo solo después de implementar transacciones correctamente. Antes, el problema existía, solo que no era visible.

Usuarios, roles y permisos

Crear usuario + asignar roles + enviar notificación es un flujo clásico que debe ser atómico.

Errores comunes al usar transacciones en Laravel

Enviar emails o eventos dentro de la transacción

Uno de los errores más comunes que he visto:

  • ❌ Enviar correos dentro de la transacción
  • ✅ Disparar eventos después del commit

Las transacciones no controlan sistemas externos.

Motores de base de datos sin soporte

No todos los motores soportan transacciones:

✅ InnoDB

❌ MyISAM

Esto es clave: si el motor no las soporta, el código no fallará… pero tampoco funcionará como esperas.

Buenas prácticas y consideraciones avanzadas

Transacciones anidadas

Laravel las soporta, pero la transacción externa es la que manda:

DB::transaction(function () {
   DB::transaction(function () {
       // transacción interna
   });
});

Performance y consultas largas

Una transacción abierta por mucho tiempo:

  • Bloquea registros
  • Reduce concurrencia

Mi regla práctica: transacciones cortas y precisas.

Testing con transacciones

En tests automáticos, las transacciones son oro puro:

use Illuminate\Foundation\Testing\DatabaseTransactions;
class ExampleTest extends TestCase
{
   use DatabaseTransactions;
}

Cada test revierte los cambios automáticamente.

Conclusión

Las operaciones transaccionales en Laravel no son un extra: son una necesidad cuando trabajas con procesos reales. Cada vez que varias operaciones dependen entre sí, una transacción es la diferencia entre un sistema confiable y uno frágil.

Desde que las incorporé de forma consistente, los bugs por datos inconsistentes prácticamente desaparecieron.

Ya hemos aprendido ha hacer muchas operaciones con la base de datos, pero, lo mejor es hacerlo de manera optima, aprendiendo a realizar consultas con Eloquent de manera eficiente.

Acepto recibir anuncios de interes sobre este Blog.

Aprenderemos a emplear las Operaciones transaccionales en la base de datos con Laravel para que podamos realizar varias operaciones en una misma unidad de tarea en Laravel.

| 👤 Andrés Cruz

🇺🇸 In english