¡Vamos a repasar otra vez los tipos de transacciones! En el mundo de las transacciones en bases de datos hay tres grandes "villanos" que pueden fastidiarte el día: Dirty Read, Non-Repeatable Read y Phantom Read. Estas "anomalías" aparecen por un nivel de aislamiento insuficiente en las transacciones. Hoy vamos a ver quiénes son estos villanos, cómo se manifiestan y —lo más importante— cómo luchar contra ellos.
Antes de pasar a los ejemplos, vamos a recordar qué significan.
Dirty Read (Lectura sucia):
Lees datos que han sido modificados, pero la transacción que los cambió aún no ha hechoCOMMITo, peor aún, puede hacerROLLBACK. Es como si mandaras dinero a un colega, miraras tu saldo y vieras que estás en la ruina, pero luego te arrepientes y te devuelves el dinero. ¡Magia!Non-Repeatable Read (Lectura no repetible):
Lees los mismos datos dos veces dentro de una transacción, pero entre las dos lecturas otra transacción los ha cambiado y ves dos resultados diferentes. Es como mirar tu fecha de nacimiento en el DNI, luego se lo das a un amigo que cambia los números, y cuando vuelves a mirar, tu fecha ha cambiado.Phantom Read (Lectura fantasma):
Haces la misma consulta dos veces, pero la segunda vez ves filas extra añadidas por otra transacción. Es como contar la gente en una sala y alguien mete a más colegas sin que te des cuenta.
Problema de Dirty Read
Supón que tenemos una tabla accounts:
CREATE TABLE accounts (
account_id SERIAL PRIMARY KEY,
owner TEXT NOT NULL,
balance NUMERIC(10, 2) NOT NULL
);
INSERT INTO accounts (owner, balance) VALUES ('Alice', 1000), ('Bob', 500);
La Transacción 1 cambia el balance, pero aún no ha terminado, y la Transacción 2 intenta leer esos datos al mismo tiempo.
Transacción 1:
BEGIN;
UPDATE accounts SET balance = balance - 200 WHERE owner = 'Alice';
-- El balance de Alice es 800, pero la transacción aún no ha terminado.
Transacción 2:
BEGIN;
SELECT balance FROM accounts WHERE owner = 'Alice'; -- Vemos balance: 800 (lectura sucia).
ROLLBACK; -- La Transacción 1 se revierte.
Ahora la Transacción 2 está usando datos incorrectos porque la Transacción 1 canceló los cambios. Esto se puede evitar usando el nivel de aislamiento READ COMMITTED, que no deja ver cambios de transacciones no confirmadas.
Problema de Non-Repeatable Read
Imagina que la Transacción 1 lee datos, otra transacción los cambia, y la Transacción 1 vuelve a leer los datos. Los datos son diferentes.
Transacción 1:
BEGIN;
SELECT balance FROM accounts WHERE owner = 'Bob'; -- Vemos balance: 500.
Transacción 2 en paralelo:
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE owner = 'Bob';
COMMIT;
Transacción 1 de nuevo:
SELECT balance FROM accounts WHERE owner = 'Bob'; -- Vemos balance: 400.
COMMIT;
Fíjate cómo los datos dentro de una misma transacción han cambiado. En escenarios reales esto puede ser crítico, por ejemplo, para informes financieros. Se soluciona usando un nivel de aislamiento más alto, como REPEATABLE READ.
Problema de Phantom Read
Supón que tenemos una tabla orders:
CREATE TABLE orders (
order_id SERIAL PRIMARY KEY,
customer TEXT NOT NULL,
total NUMERIC(10, 2) NOT NULL
);
INSERT INTO orders (customer, total) VALUES ('Alice', 100), ('Bob', 200);
La Transacción 1 cuenta el número de pedidos, otra transacción añade un pedido nuevo, y la Transacción 1 vuelve a contar.
Transacción 1:
BEGIN;
SELECT COUNT(*) FROM orders; -- Vemos: 2.
Transacción 2 en paralelo:
BEGIN;
INSERT INTO orders (customer, total) VALUES ('Charlie', 300);
COMMIT;
Transacción 1 de nuevo:
SELECT COUNT(*) FROM orders; -- Vemos: 3. ¡Ha aparecido un nuevo pedido "fantasma"!
COMMIT;
Para evitar lecturas fantasma necesitas el nivel de aislamiento SERIALIZABLE, que bloquea completamente los cambios paralelos que afectan al resultado.
Formas de evitar anomalías
Niveles de aislamiento vs. Anomalías
| Nivel de aislamiento | Dirty Read |
Non-Repeatable Read |
Phantom Read |
|---|---|---|---|
Read Uncommitted |
❌ Sí | ❌ Sí | ❌ Sí |
Read Committed |
✅ No | ❌ Sí | ❌ Sí |
Repeatable Read |
✅ No | ✅ No | ❌ Sí |
Serializable |
✅ No | ✅ No | ✅ No |
¿Cómo elegir el nivel de aislamiento correcto?
- Si necesitas acceso rápido a los datos y las anomalías no son críticas (por ejemplo, analítica sobre datos antiguos) — usa
READ COMMITTED. - Si te importa la estabilidad de los datos dentro de una transacción — usa
REPEATABLE READ. - Si necesitas máxima aislamiento y consistencia — elige
SERIALIZABLE. Ojo: el rendimiento puede bajar.
Recomendaciones prácticas
Usa transacciones con el nivel de aislamiento que mejor se adapte a tu caso. Por ejemplo:
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
BEGIN;
SELECT ...;
COMMIT;
Añade índices para minimizar bloqueos de tablas y acelerar las consultas.
Usa consultas optimizadas para evitar bloqueos largos, sobre todo en el nivel SERIALIZABLE.
Errores típicos y cómo evitarlos
A veces, elegir mal el nivel de aislamiento causa conflictos y baja el rendimiento. Por ejemplo, usar SERIALIZABLE en un sistema con muchas transacciones paralelas puede causar bloqueos y "hambre" de transacciones.
Para evitar esto, analiza tus consultas, prueba el rendimiento con diferentes niveles de aislamiento y usa los índices adecuados.
GO TO FULL VERSION