Pessimistic Locking en C#: Guía Completa

Introducción: bloquear para evitar conflictos

Pessimistic Locking es una estrategia defensiva: asume que la contención es alta, así que bloquea recursos de antemano. Si dos transacciones necesitan acceder a los mismos datos, la primera los lockea, la segunda espera. Simple, predecible, evita conflictos. No hay surpresas de race conditions; el lock garantiza exclusión mutua.

El nombre "pessimistic" (pesimista) viene de la filosofía: asume lo peor (conflicto es probable), así que protege. El opuesto es optimistic locking: asume lo mejor (conflicto es raro), procede sin locks, verifica al final. Pessimistic es más conservador.

En bases de datos, pessimistic locking es FOR UPDATE: SELECT ... FROM table WHERE id=5 FOR UPDATE. Lockea la fila antes de actualizarla. Otro usuario debe esperar. Transacción 1 actualiza, commitea, libera el lock. Transacción 2 continúa. Garantizado: sin race conditions, sin conflictos.

En memoria (C#), es similar: lock (obj) { ... }. El lock bloquea acceso a obj. Crítico: dentro del lock, eres el único accediendo obj. Afuera, múltiples threads compiten. Pessimistic = siempre lock.

Ventajas: simplicidad conceptual, garantía de corrección, predecible. Desventajas: overhead de locks (contención), posible deadlock si no cuidado con orden de acquisition, posible livelocks. Para sistemas con alta contención, pessimistic es a menudo el único práctico. Para baja contención, optimistic es más eficiente.

Pessimistic Locking
Prompt: padlock on data row, minimal style.

1. Naturaleza: turnos explícitos

Imagina un restaurante con un baño compartido. Para evitar conflictos, requiere reserva previa. Pides permiso (lock), usas exclusivamente, liberas (unlock). Si está ocupado, esperas. Pessimistic: bloquea antes de usar. Es ordenado, justo, predecible. Las colas de un banco funcionan así: turno, servido exclusivamente, siguiente. Naturaleza: cuando los recursos son valiosos y la contención probable, reservar es lógico. Evita caos, garantiza orden. En vida real vemos esto en consultorios, salas de reunión, parqueos. Predictibilidad tiene un costo (espera) pero evita sorpresas (conflictos). Pessimistic es conservador: "asumo que otros quieren el recurso, así que lo reservo primero".

Semáforo
Prompt: one-lane bridge with traffic light, soft illustration.

2. Mundo Real: inventario crítico e-commerce

Una tienda online vende un producto popular. 10000 pedidos/segundo durante black friday. Stock inicial: 1000. Cada pedido decrementa stock. Sin pessimistic locking, dos pedidos simultáneos podrían decrementar stock por separado, resultando en sobreventa (vender 1001 unidades de 1000 disponibles). Desastro. Con pessimistic locking: cada pedido que quiere actualizar stock, primero lockea la fila de stock. BEGIN TRANSACTION; SELECT * FROM products WHERE id=5 FOR UPDATE; luego actualiza stock = stock - 1. Otro pedido simultáneo espera el lock. Primer pedido commitea, libera, segundo continúa. Garantizado: sin sobreventa. La consistencia es crítica en e-commerce. El costo: contención en el lock de stock. Múltiples transacciones esperan. Pero la corrección es no-negociable. Con optimistic locking, descubrirías sobreventa al final (demasiado tarde). Casos reales: mercados de bolsa (prohibido sobrevender acciones), bancos (prohibido sobregiro), tiendas online (prohibido oversell). Todos usan pessimistic locking para datos críticos.

Inventario
Prompt: inventory lock, flat infographic.

3. Implementación en C#: Código Paso a Paso

En Entity Framework, pessimistic locking se implementa con FOR UPDATE (algunos providers) o equivalentes.

using var tx = db.Database.BeginTransaction();
try
{
    // SELECT ... FOR UPDATE (bloquea la fila)
    var item = db.Items
        .FromSqlInterpolated($"SELECT * FROM Items WHERE Id = {id} FOR UPDATE")
        .AsNoTracking()
        .FirstOrDefault();
    
    if (item != null && item.Stock > 0)
    {
        item.Stock -= 1;
        db.Items.Update(item);
        db.SaveChanges();
    }
    
    tx.Commit();
}
catch
{
    tx.Rollback();
    throw;
}

La clave: FOR UPDATE lockea la fila. Si otro usuario intenta actualizar la misma fila, espera. Cuando comiteas, liberas el lock. En memoria (lock statement), es similar:

private static readonly object _lock = new();

public void UpdateInventory(int productId)
{
    lock (_lock)  // Bloquea acceso
    {
        var item = _inventory[productId];
        if (item.Stock > 0)
            item.Stock -= 1;
    }  // Libera al salir
}

El lock garantiza exclusión mutua. Mientras un thread está dentro del lock, otros esperan. Costo: contención. Pero garantía: sin race conditions. En bases de datos, deadlock es un riesgo. Siempre acquire locks en el mismo orden para evitar circular dependencies. En aplicación, usa ConcurrentDictionary o ReaderWriterLockSlim para granularidad fina si necesario.

4. Pessimistic vs Optimistic: estrategias opuestas

Pessimistic asume contención probable. Bloquea preemptivamente. Caro cuando contención es baja (muchos locks innecesarios). Pero barato cuando contención es alta (evita retries). Optimistic asume contención rara. Procede sin locks, verifica al final. Barato cuando contención es baja (sin locks). Pero caro cuando contención es alta (muchos retries). La elección depende del patrón de contención esperado. E-commerce inventario: alta contención => pessimistic. Blog comments: baja contención => optimistic. Regla heurística: >30% conflictos esperados => pessimistic. <10% => optimistic. En medio, depende del contexto. Pessimistic proporciona garantía fuerte: "mientras estés dentro del lock, eres el único". Optimistic proporciona garantía débil: "si no hay conflicto, perfecto; si hay, retres e intenta de nuevo". Para datos críticos (dinero, inventario), pessimistic. Para datos no-críticos (preferencias, vistas), optimistic.

5. Diagrama UML

Pessimistic locking no tiene estructura UML compleja. Es un patrón de comportamiento: la forma en que se ejecutan transacciones. Un Transaction objeto que comienza, lockea recursos, opera, commitea/rollback. Los locks son primitivos del OS (mutex, semáforo). El flujo: BEGIN => LOCK (esperar si necesario) => READ/MODIFY => COMMIT (release lock). El diagrama muestra estados: locked vs unlocked. Transiciones: acquire lock => locked, release => unlocked. Si alguien intenta acquire mientras locked, entra a wait queue.

UML Pessimistic
Prompt: UML pessimistic locking, clean vector.

El flujo de tiempo muestra dos transacciones. T1 inicia, lockea recurso. T2 intenta, espera. T1 modifica, commitea, libera. T2 continúa. Secuencial, sin concurrencia real, pero correcto. Este es el tradeoff: correctness vs performance.

Flow Pessimistic
Prompt: pessimistic locking flow diagram, minimal infographic.

⚠️ Cuándo NO Usar Pessimistic Locking

  • Contención baja: Si conflictos son raros (<5%), overhead de locks supera beneficio. Usa optimistic.
  • Latencia crítica: Si necesitas <10ms, esperar locks es prohibitivo. Usa optimistic o sin sincronización.
  • Transacciones largas: Si una transacción toma 1 segundo, hold locks 1 segundo. Otros esperan. Redesigna para transacciones cortas.
  • Jerarquía de locks desconocida: Riesgo de deadlock. Si no puedes garantizar orden de acquisition, evita pessimistic complejo.
  • Sistema distribuido puro: Locks globales son difíciles en red distribuida. Usa quórum, consensus, optimistic.

💪 Ejercicio

Implementa un sistema de reservas de asientos con pessimistic locking.

Especificaciones: (1) Base de datos con tabla Seats (id, seatNumber, status:available/reserved). (2) 100 asientos. (3) Múltiples usuarios quieren reservar simultáneamente. (4) Usa pessimistic locking para garantizar sin overbooking. (5) Simula 50 threads, cada uno intenta reservar aleatoriamente. (6) Mide cuántos success, cuántos fallaron, latencia de lock.

Conclusión

Pessimistic Locking sacrifica rendimiento por consistencia. Ideal para datos críticos, contención alta. Evita sobreventa, inconsistencias, violaciones de reglas de negocio. Costo: espera, deadlock potencial. Beneficio: garantía fuerte. En sistemas financieros, e-commerce, recursos críticos, pessimistic es la elección correcta. Es conservador pero confiable.