Anomalías de concurrencia
Qué pasa cuando la concurrencia queda sin control
Las bases de datos ejecutan varias transacciones a la vez, pero cuando varias manipulan los mismos datos al mismo tiempo, pueden producirse errores o anomalías.
Las cuatro anomalías de concurrencia
Lectura sucia (dirty read)
Una transacción T1 escribe un cambio. Otra transacción T2 lee ese cambio antes de que T1 haga commit. T1 hace rollback. T2 leyó datos que nunca existieron: trabajó con información basada en una transacción que se deshizo.

No hay separación entre datos confirmados y datos en progreso. Una transacción ve todo lo que otras escriben, incluso si todavía no está confirmado.
Lectura no repetible (non-repeatable read)
Una transacción lee una fila. Otra transacción modifica esa misma fila y hace commit. La primera transacción lee la misma fila otra vez y obtiene un valor distinto.

El aislamiento permite que escrituras confirmadas de otras transacciones modifiquen datos que ya fueron leídos, sin que la transacción en curso lo sepa.
Lectura fantasma (phantom read)
Una transacción ejecuta una consulta con una condición (por ejemplo, filas donde precio > 100). Otra transacción inserta una fila que también cumple esa condición y hace commit. La primera transacción ejecuta la misma consulta otra vez y aparece una fila nueva que antes no estaba.

El aislamiento bloquea filas existentes pero no impide que aparezcan filas nuevas que coincidan con la condición.
Actualización perdida (lost update)
Dos transacciones leen el mismo valor. Ambas lo modifican en base a lo que leyeron. Ambas escriben el resultado. La segunda escritura sobrescribe a la primera y su cambio se pierde. No hay datos inválidos, solo incorrectos.

| Anomalía | Qué permite | Por qué es peligrosa |
|---|---|---|
| Lectura sucia | T2 lee datos no confirmados de T1 | Trabaja con información que puede desaparecer |
| Lectura no repetible | T2 modifica datos que T1 ya leyó | T1 obtiene resultados inconsistentes |
| Lectura fantasma | T2 inserta filas que coinciden con la consulta de T1 | T1 ve resultados incompletos o inconsistentes |
| Actualización perdida | T2 sobrescribe la escritura de T1 | Los datos quedan consistentes pero incorrectos |
Cada anomalía en acción con SQL
Las siguientes simulaciones asumen una sesión por transacción.
❌ Lectura sucia
-- Sesión 1 | -- Sesión 2
BEGIN; |
UPDATE cuentas SET saldo=200 |
WHERE id='A'; |
| BEGIN;
| SELECT saldo FROM cuentas
| WHERE id='A';
| -- lee 200 (no confirmado)
ROLLBACK; |
-- saldo de A vuelve a 500 |
| -- Sesión 2 trabajó con 200
| -- que nunca existió realmente
❌ Lectura no repetible
-- Sesión 1 | -- Sesión 2
BEGIN; |
SELECT saldo FROM cuentas |
WHERE id='A'; |
-- lee 500 |
| BEGIN;
| UPDATE cuentas SET saldo=700
| WHERE id='A';
| COMMIT;
SELECT saldo FROM cuentas |
WHERE id='A'; |
-- lee 700 (DISTINTO) |
-- misma consulta, mismo id, |
-- mismo resultado? NO |
COMMIT; |
❌ Lectura fantasma
-- Sesión 1 | -- Sesión 2
BEGIN; |
SELECT * FROM productos |
WHERE precio > 100; |
-- devuelve 2 filas |
| BEGIN;
| INSERT INTO productos
| (id, precio) VALUES (3, 150);
| COMMIT;
SELECT * FROM productos |
WHERE precio > 100; |
-- devuelve 3 filas (¡una más!) |
-- apareció un fantasma |
COMMIT; |
❌ Actualización perdida
-- Sesión 1 | -- Sesión 2
BEGIN; |
SELECT stock FROM productos |
WHERE id=1; |
-- lee 10 |
| BEGIN;
| SELECT stock FROM productos
| WHERE id=1;
| -- lee 10
UPDATE productos SET stock=11 |
WHERE id=1; |
-- escribe 11 |
| UPDATE productos SET stock=11
| WHERE id=1;
| -- escribe 11 (basado en 10)
COMMIT; |
| COMMIT;
-- stock final: 11 |
-- debería ser: 12 |
-- se perdió una actualización |
Malentendidos comunes sobre las anomalías
Error: Las lecturas sucias solo pasan cuando una transacción se cae.
Una lectura sucia ocurre cuando T2 lee datos de T1 y T1 hace rollback. No importa si T1 se cayó o decidió deshacer el cambio manualmente. El problema es leer datos que no están confirmados, independientemente de la causa del rollback.
Error: Lectura no repetible y fantasma son lo mismo.
No. En la lectura no repetible cambia el valor de una fila existente. En el fantasma aparece una fila nueva que cumple la condición. Una modifica datos, la otra modifica el conjunto de resultados.
Error: Las actualizaciones perdidas solo pasan si usamos Read Uncommitted.
Falso. La actualización perdida puede ocurrir incluso en Read Committed si el programa lee el valor, lo modifica en la aplicación y luego escribe. Dos instancias del mismo programa pueden leer el mismo valor, modificarlo y sobrescribirse mutuamente sin que el motor lo detecte.