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.
- 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.
- 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
});
Error muy común que se debe evitar
$product = Product::find($id)->lockForUpdate();
$product = Product::where('id', $id)
->lockForUpdate()
->first();