PostgreSQL
Niveles de aislamiento y anomalías
Niveles de aislamiento, anomalías de lectura y comportamiento de Read Committed, Repeatable Read y Serializable en PostgreSQL.
- Última actualización
- Actualizada
- Nivel
- Profundización
PostgreSQL
Niveles de aislamiento, anomalías de lectura y comportamiento de Read Committed, Repeatable Read y Serializable en PostgreSQL.
El aislamiento no se elige por prestigio. Se elige según las anomalías que una operación debe impedir, las garantías que ya ofrecen constraints y locks, y la capacidad de la aplicación para reintentar transacciones abortadas.
Varias transacciones pueden ejecutarse al mismo tiempo y observar estados diferentes. El nivel de aislamiento define qué snapshot utilizan y qué anomalías concurrentes pueden aparecer.
más concurrencia
↔
más coordinación y posibles abortsEl objetivo no es impedir toda interacción concurrente. Es producir resultados compatibles con el contrato del negocio a un coste razonable.
PostgreSQL expone:
READ UNCOMMITTED.READ COMMITTED.REPEATABLE READ.SERIALIZABLE.Pero READ UNCOMMITTED se comporta como READ COMMITTED; PostgreSQL no permite dirty reads de datos no confirmados.
Además, la implementación concreta de REPEATABLE READ evita algunas anomalías que el estándar permite, como phantom reads, aunque todavía puede permitir write skew.
Leer un cambio no confirmado que luego podría revertirse.
PostgreSQL no lo permite.
La misma fila devuelve valores diferentes dentro de una transacción porque otra transacción confirmó un cambio.
Repetir una condición devuelve un conjunto distinto porque aparecieron o desaparecieron filas.
Dos procesos leen un valor, calculan uno nuevo y uno sobrescribe el cambio del otro.
Dos transacciones leen el mismo conjunto, actualizan filas distintas y juntas violan una regla global.
El resultado concurrente no equivale a ningún orden serial posible.
Es el nivel predeterminado.
Cada statement obtiene un snapshot nuevo:
BEGIN
SELECT → snapshot A
otra transacción COMMIT
SELECT → snapshot B
COMMITEjemplo:
BEGIN;
SELECT status FROM orders WHERE id = 1; -- pending
-- otro proceso cambia a confirmed y confirma
SELECT status FROM orders WHERE id = 1; -- confirmed
COMMIT;Esto no es corrupción. Es la garantía del nivel.
Una sentencia puede esperar a un writer concurrente. Cuando el otro confirma, PostgreSQL reevaluará la condición sobre la versión actualizada según las reglas del comando.
Ejemplo seguro:
UPDATE inventory
SET quantity = quantity - 1
WHERE id = $1
AND quantity >= 1
RETURNING quantity;La comprobación y modificación forman una sentencia atómica. Aunque dos sesiones compitan, la condición se evalúa sobre una versión apropiada y solo una puede consumir la última unidad.
SELECT quantity = 10
calcular 9 en aplicación
UPDATE quantity = 9Dos transacciones pueden leer 10 y ambas escribir 9. Se pierde un decremento.
Alternativas:
quantity = quantity - 1.SELECT ... FOR UPDATE.No es un nivel “débil” de forma absoluta. Puede ser correcto cuando la operación está bien modelada.
PostgreSQL utiliza un snapshot estable para la transacción.
BEGIN ISOLATION LEVEL REPEATABLE READ;Después de adquirir el snapshot, los SELECT posteriores observan la misma vista de datos confirmados, aunque otras transacciones hagan commit.
Útil para:
Si intentas modificar una fila que cambió desde el snapshot, PostgreSQL puede abortar con:
could not serialize access due to concurrent updateAunque el nivel se llame repeatable read, este error usa la familia de serialization failure y la transacción debe reintentarse completa.
Ejemplo: al menos un médico debe permanecer de guardia.
Estado inicial:
Alice on_call = true
Bob on_call = trueT1:
lee que Bob está de guardia
pone Alice = falseT2:
lee que Alice está de guardia
pone Bob = falseActualizan filas distintas. Ambas pueden confirmar en repeatable read y dejar cero médicos.
El snapshot fue estable, pero la regla depende del conjunto completo.
Opciones:
SERIALIZABLE con retries.La solución depende de la regla y frecuencia de conflictos.
PostgreSQL implementa Serializable Snapshot Isolation.
No ejecuta necesariamente una transacción a la vez. Permite concurrencia y rastrea dependencias de lectura/escritura. Cuando detecta un ciclo peligroso, aborta una transacción para preservar una historia equivalente a algún orden serial.
BEGIN ISOLATION LEVEL SERIALIZABLE;Serializable utiliza locks conceptuales SIReadLock para registrar lecturas relevantes. No bloquean writers como un row lock tradicional; ayudan a detectar dependencias peligrosas.
Pueden observarse en pg_locks, pero no deben interpretarse como bloqueos normales.
SQLSTATE:
40001La aplicación debe:
Puede producir muchos aborts si:
A veces una unique constraint o un row lock específico es más simple y eficiente.
BEGIN TRANSACTION
ISOLATION LEVEL SERIALIZABLE
READ ONLY
DEFERRABLE;PostgreSQL puede esperar al inicio hasta obtener un snapshot seguro. Después, una transacción de solo lectura puede ejecutar sin riesgo de serialization failure por dependencias peligrosas.
Es útil para reportes largos consistentes, pero el inicio puede esperar.
UPDATE counters
SET value = value + 1
WHERE id = $1;Suele evitar lost update sobre una fila.
UPDATE products
SET price = $1,
version = version + 1
WHERE id = $2
AND version = $3;Cero filas indica conflicto.
SELECT * FROM products
WHERE id = $1
FOR UPDATE;Bloquea antes de decidir.
Protege invariantes más amplias mediante detección y aborts.
No existe una opción universal.
Regla:
El total de órdenes pending de un customer no puede superar su credit_limit.
Patrón en READ COMMITTED:
T1 calcula total 900
T2 calcula total 900
límite 1000
ambas crean order de 100
resultado 1100Opciones:
El nivel por sí solo no sustituye el diseño.
Una foreign key puede bloquear keys referenciadas para garantizar que el parent no desaparece. La garantía de integridad interactúa con locks independientemente del snapshot normal.
En repeatable read, una consulta repetida utiliza el mismo snapshot y no ve filas nuevas confirmadas después.
Pero esto no significa que una decisión basada en “no existe ninguna fila” sea automáticamente segura contra escrituras concurrentes. La otra transacción puede insertar una fila que tu snapshot no ve. Unique constraints o serializable protegen la regla.
No necesitas configurar toda la aplicación al nivel máximo.
Ejemplos:
Puedes establecerlo por transacción.
En repeatable read/serializable, el momento exacto de adquisición del snapshot depende de la primera operación relevante.
El retry puede duplicarlo. Usa outbox o idempotencia.
No debe reintentar errores permanentes como unique violation salvo que el dominio lo espere.
Puede causar starvation si los retries no tienen límite/backoff.
Conserva snapshot antiguo y puede contribuir a bloat.
Pueden coordinar la regla, pero todos los writers deben respetarlos.
Convierte conflictos normales en errores visibles.
Protege snapshot, no toda regla de conjunto.
Una sentencia mejor o constraint puede ser más directa.
Las decisiones previas pertenecen al intento abortado.
Incrementa conflictos y retiene recursos.
Ver una fila no impide que otra transacción la modifique.
Usa dos sesiones y controla el orden:
T1 BEGIN
T2 BEGIN
T1 read
T2 read
T1 write
T2 write
T1 COMMIT
T2 COMMITPrueba:
Una prueba secuencial no demuestra seguridad concurrente.
regla local en una fila
→ update atómico / row lock
unicidad o referencia
→ constraint
snapshot consistente de lectura
→ repeatable read
invariante de conjunto compleja
→ serializable o lock coordinador
conflictos raros con versión del cliente
→ optimistic lockingLocks, deadlocks y concurrencia explícita explica cómo coordinar recursos concretos cuando el snapshot no basta.