Ejercicio 1 — La raza
- Con constraint: 1 reserva creada + 1
IntegrityError(la BD rechaza al perdedor). Los datos sobreviven; la experiencia del perdedor depende de tu manejo (409, no 500). - Sin constraint: 2 reservas del mismo asiento — el negocio vendió dos veces la misma silla. Este es EL experimento del curso: la integridad vive en la BD, no en la buena voluntad del código (00b/03/10, la tríada).
Ejercicio 2 — FOR UPDATE
- El thread 2 espera en su SELECT hasta el commit del 1; al despertar ve el asiento ocupado → su rama de negocio lanza conflicto. 1 reserva. 2.
nowaitlanzaDatabaseError/OperationalError(psycopg:could not obtain lock) — se traduce a 409 Conflict con mensaje de dominio. 3. Conskip_lockedcada thread toma su fila libre sin bloquearse: es el patrón workers (29).
Ejercicio 3 — Deadlock
- PostgreSQL detecta el círculo y mata UNA transacción:
Deadlock detected(psycopg:DeadlockDetected), con rollback de la víctima. La otra continúa. - Con orden global por id, el círculo no se forma: el segundo thread espera el primer lock en el mismo asiento y luego toma el otro ya libre.
- Reintento: captura
django.db.utils.OperationalError(o el código 40P01) y reejecuta la función una vez tras 50-100ms. Con orden global no hará falta, pero la red de seguridad es gratis — y el patrón reaparece con SERIALIZABLE.
Ejercicio 4 — Aislamiento
- READ COMMITTED: A ve el precio nuevo en su segunda lectura dentro de la misma transacción (cada statement lee el snapshot más reciente): lecturas no repetibles, tal como el nivel promete.
- REPEATABLE READ: A sigue viendo el precio viejo (snapshot al inicio de la transacción).
- Regla: bloqueo puntual (
FOR UPDATE) para filas concretas que vas a escribir; nivel SERIALIZABLE/REPEATABLE READ para invariantes multi-fila complejas que asumen reintentos. TicketFlow: READ COMMITTED + bloqueos — el 95% de los backends.
Ejercicio 5 — La transacción definitiva
- 5 threads / 5 asientos: 5 reservas 201; los bloqueos son por filas distintas → sin contención (si bloquearas la TABLA entera, los verías serializarse — esta es la diferencia entre lock de fila y de tabla).
- Mismo asiento: 1×201 y 1×409 con cuerpo de dominio (
seat_already_reserved). - Queries esperadas: ~4-6 (SELECT for update, checks, INSERT reserva, INSERT items). Tiempo total de las 5: del orden de decenas de ms (los bloqueos paralelos por fila no se pelean).
Resumen del profesor
- atomic() para atomicidad; FOR UPDATE para exclusión; orden global para no abrazarse; constraint como red de seguridad; lo lento FUERA de la transacción.
- READ COMMITTED + bloqueos explícitos es la receta del 95%; SERIALIZABLE se paga con reintentos.
- Esta lección cierra el círculo que abrió la 00b: la invariante I1 ahora es demostrable bajo concurrencia real.
Después de la corrección: Lección 11 — Migraciones versionadas.