Niveles de aislamiento de transacciones

Avanzado
SQL
SQL
Actualizado: 18/04/2026

Diagrama: Sql niveles aislamiento transacciones

Los 4 niveles ISO

El estándar SQL define 4 niveles de aislamiento en orden creciente de garantías:

| Nivel | Dirty read | Non-repeatable read | Phantom read | |-------|------------|---------------------|--------------| | READ UNCOMMITTED | ✓ posible | ✓ posible | ✓ posible | | READ COMMITTED | ✗ | ✓ posible | ✓ posible | | REPEATABLE READ | ✗ | ✗ | ✓ posible | | SERIALIZABLE | ✗ | ✗ | ✗ |

El estándar define qué fenómenos debe prevenir cada nivel, pero no cómo implementarlos. Cada motor elige su estrategia (bloqueos, MVCC, serialization detection).

Los 3 fenómenos

Dirty read (lectura sucia)

Una transacción lee datos escritos por otra antes de que esa otra haga COMMIT.

T1: UPDATE cuentas SET saldo = 1000 WHERE id = 1;
T2: SELECT saldo FROM cuentas WHERE id = 1;  -- lee 1000 (sucio)
T1: ROLLBACK;  -- la escritura nunca existio
T2 trabajo con un dato que nunca fue real.

READ UNCOMMITTED lo permite. READ COMMITTED y superiores lo bloquean. En PostgreSQL, READ UNCOMMITTED se comporta internamente como READ COMMITTED (no hay dirty reads posibles por implementación). En MySQL InnoDB permite dirty reads con UNCOMMITTED.

Non-repeatable read (lectura no repetible)

Una transacción lee la misma fila dos veces y obtiene valores diferentes porque otra transacción la modificó y confirmó en medio.

T1 (READ COMMITTED): SELECT saldo FROM cuentas WHERE id = 1;  -- 100
T2: UPDATE cuentas SET saldo = 200 WHERE id = 1; COMMIT;
T1: SELECT saldo FROM cuentas WHERE id = 1;  -- 200 (diferente!)

READ COMMITTED lo permite. REPEATABLE READ y superiores lo bloquean (T1 seguiría viendo 100 hasta su propio COMMIT).

Phantom read (lectura fantasma)

Una transacción ejecuta la misma query de rango dos veces y obtiene número distinto de filas porque otra transacción insertó/borró filas que coinciden con el filtro.

T1 (REPEATABLE READ): SELECT COUNT(*) FROM pedidos WHERE cliente_id = 5;  -- 3
T2: INSERT INTO pedidos (cliente_id, ...) VALUES (5, ...); COMMIT;
T1: SELECT COUNT(*) FROM pedidos WHERE cliente_id = 5;  -- 4 (fantasma)

REPEATABLE READ del estándar lo permite. SERIALIZABLE lo bloquea.

En PostgreSQL, REPEATABLE READ sí previene phantoms (implementación con snapshot isolation). Es más estricto que el estándar. Igual ocurre en MySQL InnoDB para lecturas simples, aunque no para SELECT ... FOR UPDATE.

Cambiar el nivel de aislamiento

La sintaxis estándar:

-- Nivel global de la sesion (PostgreSQL)
SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL REPEATABLE READ;

-- Nivel solo para la siguiente transaccion
BEGIN ISOLATION LEVEL SERIALIZABLE;
-- ... consultas ...
COMMIT;

-- Nivel dentro de una transaccion (antes del primer SELECT/UPDATE)
BEGIN;
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
-- ...
COMMIT;

En MySQL la sintaxis es casi idéntica:

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
START TRANSACTION;
-- ...
COMMIT;

Niveles por defecto

| Motor | Default | |-------|---------| | PostgreSQL | READ COMMITTED | | MySQL InnoDB | REPEATABLE READ | | SQL Server | READ COMMITTED (con snapshot opcional) | | Oracle | READ COMMITTED |

El default de MySQL es más estricto que el de PostgreSQL. Al migrar de uno a otro, las transacciones pueden comportarse distinto ante la concurrencia.

SERIALIZABLE en PostgreSQL: SSI

PostgreSQL implementa Serializable Snapshot Isolation (SSI). En lugar de bloquear, cada transacción corre aislada como en REPEATABLE READ pero el motor monitoriza los accesos cruzados. Si detecta una dependencia que no permitiría un orden serie, aborta una de las transacciones con el error:

ERROR:  could not serialize access due to read/write dependencies among transactions
HINT:  The transaction might succeed if retried.

El patrón de uso es siempre reintentar:

import psycopg2
from psycopg2.errors import SerializationFailure
import time

def ejecutar_con_retry(conn, logica):
    for intento in range(5):
        try:
            with conn:
                with conn.cursor() as cur:
                    cur.execute("SET TRANSACTION ISOLATION LEVEL SERIALIZABLE")
                    logica(cur)
                    return
        except SerializationFailure:
            time.sleep(0.1 * (2 ** intento))
    raise Exception("Demasiados reintentos")

SERIALIZABLE es más lento que READ COMMITTED, pero más simple de razonar: si cada transacción es correcta por sí sola, la concurrencia no introducirá errores.

Ejemplo clásico: transferencia entre cuentas

-- Transferencia: quitar 100 de A y sumar 100 a B
BEGIN ISOLATION LEVEL SERIALIZABLE;
UPDATE cuentas SET saldo = saldo - 100 WHERE id = 'A';
UPDATE cuentas SET saldo = saldo + 100 WHERE id = 'B';
COMMIT;

Con READ COMMITTED esto ya es correcto si A y B son distintos (el UPDATE bloquea la fila individualmente). El problema aparece cuando hay invariantes cruzadas: "la suma de A + B debe ser siempre >= 0".

-- Si dos transferencias concurrentes intentan retirar 100 cada una
-- y el saldo inicial es 150, con READ COMMITTED ambas pueden
-- leer 150 y escribir 50 sin ver la otra, dejando saldo = -50.
-- Solo SERIALIZABLE lo garantiza, o el uso explicito de SELECT FOR UPDATE.

SELECT FOR UPDATE: bloqueo explícito

Alternativa a subir el nivel de aislamiento: bloquear explícitamente las filas a leer:

BEGIN;
SELECT saldo FROM cuentas WHERE id = 'A' FOR UPDATE;  -- bloquea hasta COMMIT
SELECT saldo FROM cuentas WHERE id = 'B' FOR UPDATE;
-- logica de la transferencia...
UPDATE ...;
COMMIT;

FOR UPDATE marca las filas como bloqueadas a nivel de fila: otras transacciones que intenten SELECT FOR UPDATE o UPDATE sobre ellas esperarán.

Variantes útiles:

  • FOR NO KEY UPDATE: bloqueo menos estricto, permite FK sin problemas.
  • FOR SHARE: otros pueden leer con FOR SHARE pero no modificar.
  • FOR UPDATE NOWAIT: si ya está bloqueada, falla inmediatamente en lugar de esperar.
  • FOR UPDATE SKIP LOCKED: omite las filas bloqueadas, útil para colas de trabajo.
-- Patron de cola: consumidor que toma tareas pendientes
BEGIN;
SELECT id, payload FROM tareas
WHERE estado = 'pendiente'
ORDER BY fecha
LIMIT 1
FOR UPDATE SKIP LOCKED;
-- procesar...
UPDATE tareas SET estado = 'completada' WHERE id = ?;
COMMIT;

Elegir el nivel correcto

  • READ COMMITTED: para la mayoría de OLTP. Buen compromiso rendimiento/consistencia.
  • REPEATABLE READ: cuando una transacción necesita ver una foto consistente (reportes transaccionales, validaciones cruzadas).
  • SERIALIZABLE: cuando hay invariantes cruzadas entre tablas que no se pueden expresar con un simple FOR UPDATE. Coste: asumir que alguna transacción abortará y hay que reintentar.
  • READ UNCOMMITTED: prácticamente nunca. Solo casos muy específicos como reports aproximados donde ver datos inconsistentes es tolerable.

Coste de rendimiento

| Nivel | Overhead | |-------|----------| | READ COMMITTED | Bajo | | REPEATABLE READ | Bajo (snapshot) | | SERIALIZABLE | Medio-alto (predicate locks + retry) |

En producción, monitoriza pg_stat_database.xact_commit y xact_rollback para detectar aumentos anómalos de rollbacks por serialization errors. Si son muchos, analiza si puedes bajar el nivel para las transacciones que no los necesitan.

Alan Sastre - Autor del tutorial

Alan Sastre

Ingeniero de Software y formador, CEO en CertiDevs

Ingeniero de software especializado en Full Stack y en Inteligencia Artificial. Como CEO de CertiDevs, SQL es una de sus áreas de expertise. Con más de 15 años programando, 6K seguidores en LinkedIn y experiencia como formador, Alan se dedica a crear contenido educativo de calidad para desarrolladores de todos los niveles.

Más tutoriales de SQL

Explora más contenido relacionado con SQL y continúa aprendiendo con nuestros tutoriales gratuitos.

Aprendizajes de esta lección

Identificar los 4 niveles de aislamiento del estandar ISO y los fenomenos que cada uno permite. Reproducir un dirty read, non-repeatable read y phantom read en sesiones paralelas. Comparar la implementacion de REPEATABLE READ en MySQL InnoDB (snapshot isolation) vs ANSI SQL. Entender SERIALIZABLE en PostgreSQL (SSI - Serializable Snapshot Isolation) y como maneja los conflictos. Elegir el nivel adecuado segun el caso de uso: OLTP critico, reporting, auditoria.

Cursos que incluyen esta lección

Esta lección forma parte de los siguientes cursos estructurados con rutas de aprendizaje