Niveles de aislamiento
El nivel de aislamiento determina cómo las transacciones concurrentes interactúan entre sí. MySQL soporta los cuatro niveles definidos por el estándar SQL.
Problemas de concurrencia
Cuando múltiples transacciones se ejecutan simultáneamente, pueden ocurrir estos problemas:
| Problema | Descripción |
|---|---|
| Lectura sucia (dirty read) | Leer datos que otra transacción aún no ha confirmado |
| Lectura no repetible (non-repeatable read) | Obtener resultados diferentes al leer la misma fila dos veces |
| Lectura fantasma (phantom read) | Obtener filas diferentes al ejecutar la misma consulta dos veces |
Los cuatro niveles
| Nivel | Lectura sucia | Lectura no repetible | Lectura fantasma |
|---|---|---|---|
| READ UNCOMMITTED | Posible | Posible | Posible |
| READ COMMITTED | No | Posible | Posible |
| REPEATABLE READ | No | No | Posible |
| SERIALIZABLE | No | No | No |
El nivel predeterminado en MySQL (InnoDB) es REPEATABLE READ.
Ver y cambiar el nivel de aislamiento
-- Ver el nivel actual
SELECT @@transaction_isolation;| @@transaction_isolation |
|---|
| REPEATABLE-READ |
-- Cambiar para la próxima transacción
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
-- Cambiar para la sesión
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
-- Cambiar globalmente (requiere privilegios)
SET GLOBAL TRANSACTION ISOLATION LEVEL READ COMMITTED;READ UNCOMMITTED
El nivel más permisivo. Permite lecturas sucias: una transacción puede ver cambios no confirmados de otra.
Conexión 1:
SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
START TRANSACTION;
UPDATE productos SET precio = 1.00 WHERE id = 1;
-- No hacemos COMMIT todavíaConexión 2:
SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
START TRANSACTION;
-- Lee el precio NO confirmado (lectura sucia)
SELECT precio FROM productos WHERE id = 1;
-- Resultado: 1.00 (dato no confirmado)
COMMIT;Conexión 1:
-- Revierte el cambio
ROLLBACK;
-- La conexión 2 leyó un dato que nunca existió realmenteEste nivel es peligroso y rara vez se usa en producción.
READ COMMITTED
Cada lectura ve solo datos confirmados. Pero si otra transacción confirma cambios durante tu transacción, los verás.
Conexión 1:
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
START TRANSACTION;
SELECT precio FROM productos WHERE id = 1;
-- Resultado: 1299.99Conexión 2:
START TRANSACTION;
UPDATE productos SET precio = 1399.99 WHERE id = 1;
COMMIT;Conexión 1:
-- La misma consulta ahora devuelve un valor diferente
SELECT precio FROM productos WHERE id = 1;
-- Resultado: 1399.99 (lectura no repetible)
COMMIT;-- Restaurar
UPDATE productos SET precio = 1299.99 WHERE id = 1;REPEATABLE READ (predeterminado)
Las lecturas dentro de una transacción siempre devuelven los mismos datos, independientemente de los cambios que otras transacciones confirmen.
Conexión 1:
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
START TRANSACTION;
SELECT precio FROM productos WHERE id = 1;
-- Resultado: 1299.99Conexión 2:
START TRANSACTION;
UPDATE productos SET precio = 1499.99 WHERE id = 1;
COMMIT;Conexión 1:
-- La misma consulta devuelve el mismo resultado
SELECT precio FROM productos WHERE id = 1;
-- Resultado: 1299.99 (lectura repetible)
COMMIT;
-- Fuera de la transacción, se ve el cambio
SELECT precio FROM productos WHERE id = 1;
-- Resultado: 1499.99-- Restaurar
UPDATE productos SET precio = 1299.99 WHERE id = 1;Lecturas fantasma en REPEATABLE READ
InnoDB usa bloqueos de rango (next-key locking) para prevenir la mayoría de lecturas fantasma, incluso en REPEATABLE READ:
Conexión 1:
START TRANSACTION;
SELECT COUNT(*) FROM productos WHERE categoria_id = 1;
-- Resultado: 5Conexión 2:
START TRANSACTION;
INSERT INTO productos (nombre, precio, stock, categoria_id)
VALUES ('Nuevo fantasma', 100.00, 1, 1);
COMMIT;Conexión 1:
-- InnoDB previene el fantasma con su snapshot
SELECT COUNT(*) FROM productos WHERE categoria_id = 1;
-- Resultado: 5 (no ve la nueva fila)
COMMIT;DELETE FROM productos WHERE nombre = 'Nuevo fantasma';SERIALIZABLE
El nivel más estricto. Las transacciones se ejecutan como si fueran secuenciales. Todas las lecturas se convierten en SELECT ... FOR SHARE.
Conexión 1:
SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE;
START TRANSACTION;
SELECT * FROM productos WHERE categoria_id = 1;
-- InnoDB coloca un bloqueo compartido en las filas leídasConexión 2:
SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE;
START TRANSACTION;
-- Esta operación se BLOQUEA hasta que la conexión 1 termine
INSERT INTO productos (nombre, precio, stock, categoria_id)
VALUES ('Serial test', 100.00, 1, 1);
-- Espera... (puede generar timeout)Conexión 1:
COMMIT;
-- Ahora la conexión 2 puede continuarComparación de rendimiento
| Nivel | Concurrencia | Consistencia | Bloqueos |
|---|---|---|---|
| READ UNCOMMITTED | Alta | Baja | Mínimos |
| READ COMMITTED | Media-Alta | Media | Pocos |
| REPEATABLE READ | Media | Alta | Moderados |
| SERIALIZABLE | Baja | Máxima | Máximos |
Cuándo usar cada nivel
| Nivel | Caso de uso |
|---|---|
| READ UNCOMMITTED | Reportes aproximados donde la velocidad importa más que la precisión |
| READ COMMITTED | Aplicaciones web típicas con mucha lectura y escritura |
| REPEATABLE READ | Predeterminado de MySQL, adecuado para la mayoría de aplicaciones |
| SERIALIZABLE | Operaciones financieras críticas donde la consistencia es absoluta |
Bloqueos de fila: FOR UPDATE y FOR SHARE
Dentro de una transacción, puedes solicitar bloqueos explícitos:
-- FOR UPDATE: bloqueo exclusivo (nadie más puede leer ni escribir)
START TRANSACTION;
SELECT stock FROM productos WHERE id = 1 FOR UPDATE;
-- Otras transacciones no pueden modificar ni bloquear esta fila
UPDATE productos SET stock = stock - 1 WHERE id = 1;
COMMIT;-- FOR SHARE: bloqueo compartido (otros pueden leer pero no escribir)
START TRANSACTION;
SELECT stock FROM productos WHERE id = 1 FOR SHARE;
-- Otras transacciones pueden leer pero no modificar
COMMIT;| Tipo de bloqueo | Permite lectura | Permite escritura | Uso |
|---|---|---|---|
| FOR SHARE | Sí | No | Verificar que un dato no cambie |
| FOR UPDATE | No | No | Modificar el dato después de leerlo |
Deadlocks
Cuando dos transacciones se bloquean mutuamente, MySQL detecta el deadlock y revierte automáticamente una de ellas:
Conexión 1:
START TRANSACTION;
UPDATE productos SET stock = stock - 1 WHERE id = 1;
-- Tiene bloqueo en producto 1Conexión 2:
START TRANSACTION;
UPDATE productos SET stock = stock - 1 WHERE id = 2;
-- Tiene bloqueo en producto 2
UPDATE productos SET stock = stock - 1 WHERE id = 1;
-- Espera el bloqueo de la conexión 1...Conexión 1:
UPDATE productos SET stock = stock - 1 WHERE id = 2;
-- Espera el bloqueo de la conexión 2...
-- DEADLOCK detectado: MySQL revierte una transacción
-- Error 1213: Deadlock found when trying to get lockPara prevenir deadlocks, accede siempre a las tablas y filas en el mismo orden.
Restaurar nivel predeterminado
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
-- Verificar
SELECT @@transaction_isolation;| @@transaction_isolation |
|---|
| REPEATABLE-READ |
En el siguiente artículo veremos el bloqueo de tablas (TABLE LOCKING).
Escrito por Eduardo Lázaro
