Módulo 2 · Bases de datos

Lección 10 — Transacciones

ACID, niveles de aislamiento, bloqueos y concurrencia optimista/pesimista en la reserva de asientos.

Publicada
En esta lección
  1. Ejercicio 1 — La raza, reproducida
  2. Ejercicio 2 — select_for_update: la exclusión funciona
  3. Ejercicio 3 — Deadlock: provócalo y arréglalo
  4. Ejercicio 4 — Nivel de aislamiento en acción
  5. Ejercicio 5 — La transacción definitiva
  6. Entrega

Django shell + threading (la 03 te dio el músculo). No mires solutions.md hasta entregar.

Preparación: python manage.py shell con dos asientos libres y dos usuarios.

Ejercicio 1 — La raza, reproducida

  1. Escribe reservar_sin_bloqueo(seat_id, user): lee esta_reservado(), duerme 0.05s, crea el item. Lánzalo con 2 threads sobre el mismo asiento. ¿Cuántas reservas se crearon? (la constraint de I1 puede salvar los datos con IntegrityError — captúrala y anota cuál thread ganó).
  2. Quita la constraint condicional (migración temporal) y repite: ¿2 reservas? Documenta el desastre y restaura.

Ejercicio 2 — select_for_update: la exclusión funciona

  1. Misma función, pero con Seat.objects.select_for_update().get(pk=...) dentro de transaction.atomic(). ¿Qué hace el thread 2? ¿Cuántas reservas al final?
  2. Cambia a nowait=True: ¿qué excepción llega al thread 2? ¿Cómo la traduces a una respuesta HTTP?
  3. Con skip_locked=True y 2 threads reservando asientos distintos del mismo evento: ¿cada uno reservó el suyo sin esperarse?

Ejercicio 3 — Deadlock: provócalo y arréglalo

  1. Thread A bloquea asiento 1 y luego el 2 (dos transacciones separadas, con sleeps). Thread B: primero el 2, luego el 1. ¿PostgreSQL mata a alguien? ¿Qué error exacto?
  2. Arregla con orden global (ambos bloquean order_by("id")). ¿Sigue pasando?
  3. Añade reintento automático: captura el error de deadlock y reintenta una vez con backoff.

Ejercicio 4 — Nivel de aislamiento en acción

  1. En READ COMMITTED (default): thread A inicia transacción y lee el precio de un evento; thread B lo cambia y commitea; A relee dentro de su transacción. ¿Vió el cambio? (lectura no repetible).
  2. Repite con REPEATABLE READ (transaction.atomic() + SET TRANSACTION ISOLATION LEVEL REPEATABLE READ). ¿Qué lee A ahora?
  3. Escribe la regla: cuándo subes el nivel y cuándo basta bloqueo puntual.

Ejercicio 5 — La transacción definitiva

Implementa reservar() de la lección completa (bloqueo ordenado + reglas + creación + I2) y demuéstrala:

  1. 5 threads, 5 asientos distintos: todos 201, sin esperas perceptibles.
  2. 2 threads, mismo asiento: 1 ganador y 1 con tu excepción de dominio → 409.
  3. Métrica final: len(connection.queries) de una reserva (debe ser un número pequeño y estable) y tiempo total de las 5 reservas del punto 1.

Entrega

Pega código, outputs y números. Con esto cerramos el corazón del módulo de datos; después Lección 11 — Migraciones versionadas.