SERIALIZABLE es el nivel de aislamiento de transacciones más alto en PostgreSQL. Este nivel garantiza que los resultados de las transacciones paralelas serán los mismos que si se ejecutaran en SECUENCIA, una tras otra. Así, ninguna anomalía de ejecución concurrente (por ejemplo, Dirty Read, Non-Repeatable Read, Phantom Read) puede ocurrir.
Dicho de forma sencilla, SERIALIZABLE asegura orden y coherencia total entre transacciones paralelas. Es como si PostgreSQL dijera: "¡Todos a la cola, colegas!"
¿Para qué sirve el nivel SERIALIZABLE? A veces quieres estar 100% seguro de que tus datos se mantienen totalmente coherentes, aunque haya cambios en paralelo. Imagina una escena en el súper, donde los cajeros atienden a los clientes al mismo tiempo. Si nadie controlara el orden, al salir del súper podría haber más productos de los que realmente se compraron. Con SERIALIZABLE eso simplemente no puede pasar.
Ejemplo de configuración del nivel SERIALIZABLE
Para establecer el nivel de aislamiento SERIALIZABLE, tienes que usar el comando:
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
Por ejemplo, vamos a crear una transacción que use este nivel:
BEGIN; -- Empezamos la transacción
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE; -- Establecemos el nivel de aislamiento
SELECT * FROM products WHERE category = 'Electronics'; -- Obtenemos la lista de productos
UPDATE products SET stock = stock - 1 WHERE product_id = 123; -- Actualizamos el stock
COMMIT; -- Confirmamos los cambios
Caso: reserva de entradas en el cine
Vamos a ver un ejemplo real donde el nivel SERIALIZABLE es imprescindible. Imagina que estás desarrollando un sistema de reservas online para un cine. Tus usuarios eligen asientos y quieres garantizar que un mismo asiento no pueda ser comprado por dos clientes a la vez.
Primero creamos una tabla para los asientos:
CREATE TABLE seats (
seat_id SERIAL PRIMARY KEY,
is_booked BOOLEAN DEFAULT FALSE
);
Ahora añadimos algunos asientos:
INSERT INTO seats (is_booked) VALUES (FALSE), (FALSE), (FALSE);
Veamos un ejemplo de transacción con SERIALIZABLE.
Así puedes hacer una reserva segura de un asiento:
BEGIN; -- Inicio de la transacción
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE; -- Nivel de aislamiento SERIALIZABLE
-- Comprobamos que el asiento está libre
SELECT is_booked FROM seats WHERE seat_id = 1;
-- Reservamos el asiento
UPDATE seats SET is_booked = TRUE WHERE seat_id = 1;
COMMIT; -- Confirmamos la reserva
Si una segunda transacción paralela intenta reservar el mismo asiento, PostgreSQL no permitirá ningún lío y lanzará un error de conflicto de serialización.
Prevención de Phantom Read
Ahora vamos a ver las "lecturas fantasma" de las que tanto queremos librarnos. Phantom Read ocurre cuando una transacción ve cambios en los datos, añadidos por otra transacción durante su ejecución. Por ejemplo, tu transacción espera un número concreto de filas, pero de repente otra transacción añade o borra filas, cambiando los resultados.
Veamos un ejemplo:
Datos antes de empezar las transacciones
| id | balance | user |
|---|---|---|
| 1 | 1000 | Alice |
| 2 | 500 | Bob |
Transacción 1
BEGIN;
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
-- Contamos usuarios con balance mayor que 400
SELECT COUNT(*) FROM accounts WHERE balance > 400;
-- Esperamos resultado: 2 (Alice y Bob)
Transacción 2
En otra sesión se ejecuta una transacción paralela:
BEGIN;
INSERT INTO accounts (id, balance, user) VALUES (3, 700, 'Charlie');
COMMIT;
Volvemos a la Transacción 1
-- Repetimos la consulta
SELECT COUNT(*) FROM accounts WHERE balance > 400;
Ahora, si no usas SERIALIZABLE, el resultado será 3 en vez de 2, porque Charlie fue añadido durante la ejecución de la Transacción 1. Eso es un Phantom Read.
Pero con SERIALIZABLE PostgreSQL garantiza que la Transacción 1 no verá a Charlie, porque su "visión del mundo" está congelada en el momento en que empezó la transacción.
Características y limitaciones del nivel SERIALIZABLE
Ya vimos cómo SERIALIZABLE ayuda a lograr un aislamiento perfecto. Pero, ¿qué hay perfecto en este mundo sin sus pegas? Vamos a ser sinceros.
Bajada de rendimiento
SERIALIZABLE necesita muchos más recursos que los niveles READ COMMITTED o REPEATABLE READ. ¿Por qué? PostgreSQL tiene que simular la ejecución secuencial de las operaciones, vigilando todos los posibles conflictos entre transacciones.
Errores de serialización
Si PostgreSQL detecta que no puede ejecutar las transacciones en el "orden perfecto", genera un error de serialización (serialization_failure) y revierte la transacción.
Ejemplo de error:
ERROR: could not serialize access due to concurrent update
Para manejar estas situaciones, podemos volver a ejecutar la transacción tras el fallo:
DO $$
DECLARE
done BOOLEAN := FALSE;
BEGIN
WHILE NOT done LOOP
BEGIN
-- Empezamos la transacción
BEGIN;
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
-- Realizamos las operaciones
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
-- Confirmamos los cambios
COMMIT;
done := TRUE; -- Salimos del bucle si todo va bien
EXCEPTION WHEN serialization_failure THEN
ROLLBACK; -- Revertimos en caso de error
END;
END LOOP;
END;
$$;
Este es el enfoque habitual en sistemas donde se usa SERIALIZABLE.
Este código está escrito usando PL-SQL. Volveremos a él más adelante. Solo quería darte un código bonito y funcional. Y de paso mostrarte para qué sirve PL-SQL :)
¿Cuándo usar SERIALIZABLE?
Este nivel de aislamiento tiene sentido donde el precio del error es muy alto:
- Transacciones financieras, como procesamiento de pagos o reparto de bonus.
- Sistemas de gestión de inventario, para evitar pedidos duplicados.
- Reservas online, donde es clave evitar conflictos al reservar recursos.
Si estás desarrollando un sistema donde los datos deben ser 100% coherentes y el rendimiento no es lo más importante, SERIALIZABLE será tu mejor colega.
GO TO FULL VERSION