CodeGym /Cursos /SQL SELF /Nivel de aislamiento REPEATABLE READ: evitando Non-Repeat...

Nivel de aislamiento REPEATABLE READ: evitando Non-Repeatable Read

SQL SELF
Nivel 40 , Lección 1
Disponible

Imagina que estás jugando a un juego online y, mientras tanto, algún tramposo mete mano en el código y mejora a su jugador, o que estás leyendo un libro en la biblioteca y alguien puede cambiar las páginas, meter capítulos nuevos o incluso cambiarte el libro entero. Bastante molesto, ¿verdad? Pues justo de este tipo de "sorpresas" te protege el nivel de aislamiento REPEATABLE READ.

REPEATABLE READ te da la garantía de que los datos que ves dentro de una transacción no van a cambiar hasta que termines esa transacción. Incluso si otra transacción intenta actualizar esos datos, la tuya estará a salvo de esos cambios.

Puntos clave:

  • Evita Dirty Read (lectura de datos que aún no han sido confirmados).
  • Y, lo más importante, evita Non-Repeatable Read. Esto significa que si lees un conjunto de datos al principio de la transacción, cuando vuelvas a leerlos tendrás los mismos datos, aunque otro usuario los haya cambiado.

Sin embargo, REPEATABLE READ no te protege de Phantom Read. Si otra transacción mete filas nuevas, pueden aparecer en tu consulta repetida. Para evitar también esa anomalía, necesitas el nivel SERIALIZABLE, pero de eso hablaremos más adelante.

Cómo poner el nivel de aislamiento REPEATABLE READ

Antes de ir a los ejemplos, vamos a ver cómo activar este nivel de aislamiento en PostgreSQL. Hay dos formas principales:

  1. Poner el nivel de aislamiento para una transacción concreta:

    SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
    BEGIN;
    -- Tus consultas
    COMMIT;
    
  2. Poner el nivel de aislamiento para toda la sesión actual:

    SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL REPEATABLE READ;
    

En el segundo caso, todas las transacciones de la sesión actual usarán REPEATABLE READ.

Ejemplo: evitando Non-Repeatable Read

Supón que tenemos una tabla accounts con la siguiente estructura:

CREATE TABLE orders (
    order_id SERIAL PRIMARY KEY,
    customer_name TEXT NOT NULL,
    status TEXT NOT NULL DEFAULT 'pendiente'
);

INSERT INTO orders (customer_name, status)
VALUES ('Alicia', 'pendiente'), ('Bob', 'pendiente');

Vamos a empezar con un escenario básico donde una transacción cambia datos y otra los lee.

Escenario sin REPEATABLE READ (nivel READ COMMITTED)

Transacción 1 empieza:

BEGIN;
SELECT balance FROM accounts WHERE account_id = 1;
-- Obtenemos: 100

Mientras tanto, Transacción 2 cambia los datos:

BEGIN;
UPDATE accounts SET balance = 150 WHERE account_id = 1;
COMMIT;

Transacción 1 sigue:

SELECT balance FROM accounts WHERE account_id = 1;
-- Obtenemos: 150 (¡los datos cambiaron!)
COMMIT;

Como ves, con el nivel READ COMMITTED los datos pueden cambiar entre dos lecturas dentro de la misma transacción. Eso es justo un Non-Repeatable Read.

Escenario con REPEATABLE READ

Ahora probemos el mismo ejemplo, pero con el nivel de aislamiento REPEATABLE READ.

Transacción 1:

SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
BEGIN;
SELECT balance FROM accounts WHERE account_id = 1;
-- Obtenemos: 100

Transacción 2:

BEGIN;
UPDATE accounts SET balance = 150 WHERE account_id = 1;
COMMIT;

Transacción 1 sigue currando:

SELECT balance FROM accounts WHERE account_id = 1;
-- Seguimos obteniendo: 100 (¡los datos no cambian!)
COMMIT;

No importa lo que cambie otra transacción, la transacción 1 ve los datos tal y como estaban al empezar. Así, Non-Repeatable Read se evita.

Cómo funciona REPEATABLE READ

PostgreSQL usa el mecanismo MVCC (Multi-Version Concurrency Control) para implementar el nivel de aislamiento REPEATABLE READ. La idea principal de MVCC es que cada transacción recibe un "snapshot" estable de la base de datos, que no cambia hasta que termina la transacción. Esto se consigue creando y gestionando varias versiones de las filas.

Cuando una transacción empieza, ve los datos tal y como estaban en el momento de su inicio. Si otra transacción hace cambios, PostgreSQL crea una nueva versión de la fila, pero la versión anterior sigue ahí para las transacciones que la están usando.

Por eso las transacciones pueden ir más lentas y consumir más memoria. Y por eso casi nadie usa el nivel de aislamiento más fuerte: es el más seguro, pero también el que más ralentiza la base de datos.

Limitaciones de REPEATABLE READ: Phantom Read

Como ya dijimos, REPEATABLE READ no te protege de Phantom Read. Para entender qué significa esto, veamos un ejemplo con consultas que trabajan con rangos de datos.

Supón que tenemos una tabla orders:

CREATE TABLE orders (
    order_id SERIAL PRIMARY KEY,
    amount NUMERIC NOT NULL
);

INSERT INTO orders (amount)
VALUES (50), (100), (150);

Transacción 1:

SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
BEGIN;
SELECT COUNT(*) FROM orders WHERE amount > 50;
-- Obtenemos: 2

Transacción 2:

BEGIN;
INSERT INTO orders (amount) VALUES (200);
COMMIT;

Transacción 1 sigue currando:

SELECT COUNT(*) FROM orders WHERE amount > 50;
-- Obtenemos: 3 (¡la nueva fila aparece en el resultado!)
COMMIT;

En este caso, la nueva fila (con amount = 200) fue añadida por otra transacción y aparece "fantasmalmente" en el resultado de la consulta de la transacción 1, aunque el nivel de aislamiento sea REPEATABLE READ.

Si necesitas evitar Phantom Read, tendrás que usar el nivel SERIALIZABLE, pero eso siempre implica sacrificar algo de rendimiento.

Ventajas y desventajas de REPEATABLE READ

El nivel de aislamiento REPEATABLE READ es una opción genial cuando necesitas estar seguro de que los datos no van a cambiar mientras dura tu transacción. En cuanto lees algo, ese valor se queda igual hasta el COMMIT, aunque alguien en otra transacción intente cambiarlo.

Este enfoque evita tanto lecturas sucias (dirty read) como lecturas no repetibles (non-repeatable read). Trabajas con los mismos datos que al principio, sin sorpresas de actualizaciones "al vuelo". Es especialmente útil cuando generas informes o tomas decisiones donde la consistencia es clave.

Por otro lado, REPEATABLE READ no puede con los llamados "fantasmas" (phantom read) — cuando aparecen filas nuevas en el resultado de una consulta que ya habías hecho en la misma transacción. Además, bajo mucha carga, este nivel puede causar conflictos entre transacciones, sobre todo si acceden mucho a los mismos datos. Esto puede llevar a bloqueos y rollbacks, aunque las consultas estén bien.

En resumen, REPEATABLE READ es un buen equilibrio entre fiabilidad y rendimiento, pero en escenarios con mucha competencia puede requerir ajustes y atención extra.

Consejos útiles y errores típicos

  • Recuerda que el nivel de aislamiento afecta al rendimiento. Usa REPEATABLE READ solo cuando de verdad necesites que los datos no cambien.
  • Confundir REPEATABLE READ y SERIALIZABLE es un error común. Si ves filas nuevas en una consulta repetida, eso es lo esperado en REPEATABLE READ.
  • Si trabajas con transacciones largas, ojo con los posibles bloqueos. Las transacciones largas pueden bloquear otras operaciones.

PostgreSQL te da varias herramientas para gestionar el aislamiento de transacciones. El nivel REPEATABLE READ es perfecto cuando necesitas estar seguro de que los datos que ya leíste en una transacción no van a cambiar.

2
Tarea
SQL SELF, nivel 40, lección 1
Bloqueada
Prevención de Non-Repeatable Read en el nivel `REPEATABLE READ`
Prevención de Non-Repeatable Read en el nivel `REPEATABLE READ`
Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION