LARAVEL: LOCKFORUPDATE VS SHAREDLOCK: BLOQUEO PESIMISTA EN ELOQUENT

El bloqueo pesimista es una de las herramientas más poderosas, y a menudo subestimadas, de Laravel para mantener la integridad de los datos en entornos concurrentes. Cuando dos usuarios intentan trabajar sobre la misma fila al mismo tiempo, pueden ocurrir que generen datos inconsistentes o pérdidas financieras. En este post se explican las diferencias entre "lockForUpdate" y "sharedLock" y cuándo usar cada uno y las mejores prácticas recomendadas.

¿Qué es el bloqueo pesimista?

El bloqueo pesimista consiste en bloquear una fila a nivel de base de datos mientras se está trabajando con ella, evitando que otros procesos la modifiquen o, en algunos casos, incluso la lean.
Laravel ofrece dos métodos principales para aplicar este tipo de bloqueo:
  • lockForUpdate → Bloqueo de escritura (exclusive lock)
  • sharedLock → Bloqueo compartido de lectura (shared lock)

lockForUpdate() – El candado de escritura

Úsarlo cuando se va a actualizar la fila. Características:
  • Bloquea completamente la fila.
  • Ningún otro proceso puede leerla ni modificarla hasta que termine la transacción.
  • Ideal para operaciones críticas donde la consistencia es vital.
Casos de uso recomendados:
  • Gestión de stock
  • Billeteras digitales (wallets)
  • Pagos y transacciones financieras
  • Cualquier operación donde hagas "lectura + modificación"

Ejemplo

DB::transaction(function () {
    $product = Product::where('id', $id)
                ->lockForUpdate()
                ->first();

    $product->decrement('stock', $quantity);
});

sharedLock() – El candado de lectura compartida

Usarlo cuando solo cuando se necesita leer la información de forma consistente, pero no se va a modificada. Características:
  • Permite que otros procesos lean la misma fila.
  • Impide que alguien modifique la fila mientras si alguien ya la estás leyendo.
  • Es menos restrictivo que lockForUpdate.
Casos de uso recomendados:
  • Mostrar información crítica que debe ser consistente (saldos, precios, estados de pedido, etc.).
  • Lecturas donde necesitas garantizar que los datos no cambien durante el proceso.

Ejemplo

DB::transaction(function () {
    $user = User::where('id', 1)
              ->sharedLock()
              ->first();
    
    // Aquí puedes mostrar datos confiables
});
Siempre usa estos bloqueos dentro de una transacción (DB::transaction).

Error muy común que se debe evitar

$product = Product::find($id)->lockForUpdate();
El código anterior está mal porque el método "find()" ya ejecutó la consulta antes de aplicar el bloqueo. El lock nunca se aplica. Solución: Encadena el lock antes de ejecutar la consulta:
$product = Product::where('id', $id)
            ->lockForUpdate()
            ->first();
Dominar lockForUpdate y sharedLock es fundamental si están desarrollando aplicaciones que manejan datos sensibles o recursos limitados. Un buen uso del bloqueo pesimista puede evitar errores costosos en producción. Siempre que haya una operación de "leer y luego modificar", pensar en lockForUpdate + transacción. Para lecturas que necesiten consistencia, sharedLock

Resumen comparativo

Aspecto lockForUpdate() sharedLock()
Tipo de bloqueo Exclusivo (escritura) Compartido (lectura)
Uso recomendado Cuando vas a actualizar Solo lectura consistente
Otros procesos pueden leer No
Otros procesos pueden modificar No No
Escenarios típicos Stock, pagos, wallets Lecturas críticas