Cuando varias operaciones acceden a los mismos datos al mismo tiempo, una decisión aparentemente sencilla puede producir resultados inesperados. Un informe puede leer cifras que cambian durante su ejecución, o dos compras pueden intentar reservar la última unidad disponible.
Los niveles de aislamiento en SQL ayudan a controlar cómo interactúan las transacciones concurrentes. Elegir uno implica valorar qué resultados necesita la aplicación y cuánto bloqueo, espera o reintento puede aceptar. En esta guía verás los niveles más habituales, sus riesgos y una forma práctica de elegir entre consistencia y concurrencia.
Qué es el aislamiento de una transacción
Una transacción agrupa operaciones que se confirman juntas con COMMIT o se descartan con ROLLBACK. El aislamiento determina qué puede observar una transacción mientras otras leen o modifican datos.
Por ejemplo, imagina que una transacción consulta el saldo de una cuenta mientras otra registra una transferencia. Según el aislamiento y el motor, la primera puede esperar, leer una versión anterior o encontrar resultados distintos en consultas sucesivas.
El aislamiento es una de las propiedades ACID. No debe confundirse con la consistencia de ACID: esta última se refiere a que las transacciones preserven las reglas de integridad de los datos. Para conseguirlo también pueden ser necesarias restricciones, operaciones atómicas y una lógica de aplicación correcta.
Tampoco existe una relación universal según la cual un aislamiento más estricto siempre ralentice todo. El impacto depende del motor, la carga y la duración de las transacciones.
Anomalías que conviene conocer
Antes de comparar niveles, resulta útil identificar los problemas que pueden aparecer:
- Lectura sucia: una transacción lee cambios de otra que todavía no se han confirmado y que podrían revertirse.
- Lectura no repetible: una transacción lee una fila dos veces y obtiene valores diferentes porque otra confirmó un cambio entre ambas lecturas.
- Lectura fantasma: una consulta basada en una condición devuelve un conjunto de filas distinto al repetirse, por inserciones o eliminaciones concurrentes.
- Actualización perdida: dos operaciones calculan cambios a partir del mismo valor y una sobrescribe el resultado de la otra.
- Sesgo de escritura: dos transacciones leen una situación compartida y modifican filas distintas; ambas se confirman, pero juntas incumplen una regla.
El sesgo de escritura puede ocurrir, por ejemplo, si dos médicos comprueban que hay dos personas de guardia y cada uno se retira por separado. El resultado deja a nadie de guardia, aunque cada transacción haya visto inicialmente una situación válida.
Los cuatro niveles de aislamiento SQL
El estándar SQL define cuatro niveles. La siguiente comparación resume las garantías clásicas, pero no sustituye la documentación del motor: algunos ofrecen protecciones adicionales o implementan los niveles de forma diferente.
| Nivel | Lecturas sucias | Lecturas no repetibles | Lecturas fantasma |
|---|---|---|---|
| Read Uncommitted | Posibles | Posibles | Posibles |
| Read Committed | Evitadas | Posibles | Posibles |
| Repeatable Read | Evitadas | Evitadas | Posibles según el estándar |
| Serializable | Evitadas | Evitadas | Evitadas |
Read Uncommitted
Es el nivel menos restrictivo del estándar. Permite que una transacción lea cambios aún no confirmados por otra. Si esos cambios se revierten, la primera habrá usado información que nunca llegó a ser definitiva.
Solo tiene sentido cuando la aplicación tolera resultados provisionales y se ha comprobado que el motor realmente ofrece ese comportamiento. Por ejemplo, PostgreSQL trata Read Uncommitted como Read Committed. No conviene asumir que seleccionar este nivel eliminará los bloqueos o mejorará el rendimiento.
Read Committed
Cada lectura ve únicamente datos confirmados. Es una opción habitual para operaciones generales que necesitan evitar lecturas sucias, pero no requieren que varias consultas dentro de la misma transacción observen una imagen idéntica.
Por ejemplo, una transacción consulta un precio, otra lo modifica y confirma el cambio, y la primera vuelve a consultarlo. Con Read Committed, la segunda lectura puede devolver el nuevo precio. Si la aplicación necesita actuar sobre el valor original, debe proteger esa decisión de otra manera.
Repeatable Read
Este nivel evita que una fila ya leída cambie desde la perspectiva de la misma transacción. En el modelo del estándar todavía pueden aparecer filas fantasma, aunque algunas implementaciones ofrecen garantías más fuertes para determinadas lecturas.
En PostgreSQL, Repeatable Read utiliza una instantánea y evita las lecturas fantasma, pero aún puede permitir sesgos de escritura. En otros motores, el comportamiento puede depender del tipo de consulta y de los bloqueos utilizados. Por eso, el nombre del nivel no basta para deducir todas sus garantías.
Puede ser útil para informes con varias consultas que deben basarse en una vista estable, siempre que se evalúe el coste de mantener la transacción abierta.
Serializable
Es el nivel más estricto: el resultado de las transacciones confirmadas debe ser equivalente al de alguna ejecución en serie. Es apropiado para reglas que involucran varias filas y que podrían romperse por decisiones concurrentes.
No significa necesariamente que todas las transacciones se ejecuten una detrás de otra. Según el motor, puede utilizar bloqueos, detectar conflictos o combinar técnicas. Algunas transacciones pueden fallar y requerir un reintento completo. Esto añade complejidad y, bajo alta contención, puede afectar al rendimiento.
Bloqueos y versiones: dos mecanismos habituales
Para aplicar el aislamiento, los motores pueden usar bloqueos, control de concurrencia multiversión o una combinación de ambos.
Un bloqueo limita qué operaciones pueden avanzar sobre determinados datos hasta que otra transacción termine. Ayuda a coordinar accesos, pero puede generar esperas o interbloqueos: situaciones en las que dos transacciones esperan recursos retenidos por la otra.
El control de concurrencia multiversión, conocido como MVCC, mantiene versiones de los datos para que algunas lecturas puedan acceder a una vista anterior mientras se realizan escrituras. Esto no elimina todos los conflictos: las escrituras siguen necesitando coordinación y algunas transacciones pueden fallar.
Los detalles importan. Una lectura ordinaria, una lectura con bloqueo y una actualización pueden comportarse de forma distinta incluso dentro del mismo nivel. Consulta la documentación del motor y prueba las consultas reales de la aplicación.
Ejemplo práctico: reservar inventario
Supón que una tienda tiene una unidad de un producto. Una implementación vulnerable podría leer el stock, comprobar en la aplicación que es mayor que cero y, después, escribir un nuevo valor. Si dos compradores hacen esa comprobación al mismo tiempo, pueden producir una venta indebida o sobrescribir un cambio.
Para un contador en una sola fila, una alternativa es expresar la condición y la modificación en una única sentencia:
UPDATE productos
SET stock = stock - 1
WHERE id = 42 AND stock > 0;
La aplicación debe comprobar cuántas filas se actualizaron: una indica que se reservó una unidad; cero indica que el producto no existe o que no había stock disponible. El motor coordina las escrituras sobre la fila, pero es importante mantener la actualización y el resto de la operación necesaria dentro de una transacción adecuada.
Una restricción como CHECK (stock >= 0), cuando el motor la aplica, añade una defensa útil. Sin embargo, no sustituye la comprobación del resultado ni resuelve por sí sola reglas que involucren varias filas, como un límite compartido entre almacenes.
Cómo elegir el nivel adecuado
No empieces por el nivel más estricto o por el más rápido en teoría. Empieza por la regla que no debe romperse y el tipo de datos que la operación necesita observar.
- Define la operación y su regla. ¿Basta con leer datos confirmados o varias consultas deben ver una imagen estable? ¿Hay una condición que abarca varias filas?
- Identifica las anomalías relevantes. Una cifra que cambia en un panel puede ser aceptable; una reserva duplicada, no.
- Revisa el comportamiento del motor. Comprueba el nivel predeterminado, las garantías reales, los bloqueos y los posibles errores de concurrencia.
- Evalúa alternativas específicas. Una actualización atómica, una restricción o un bloqueo explícito pueden proteger una operación sin elevar el aislamiento de toda la aplicación.
- Prueba con concurrencia realista. Ejecuta transacciones simultáneas y mide esperas, fallos, reintentos y resultados, no solo el tiempo de una consulta aislada.
Como orientación, Read Committed puede encajar en muchas operaciones generales; una vista estable puede justificar Repeatable Read; y una regla crítica que depende de varias lecturas y escrituras puede requerir Serializable. Son puntos de partida, no garantías automáticas.
Buenas prácticas para reducir conflictos
Mantén las transacciones cortas: evita esperar respuestas de servicios externos o interacción humana mientras permanecen abiertas. Accede a los recursos en un orden coherente cuando sea posible para reducir interbloqueos, y usa índices apropiados para limitar el trabajo y, según el motor, el alcance de algunos bloqueos.
Diseña además una estrategia de reintento para errores transitorios, como fallos de serialización o interbloqueos. El reintento debe repetir la transacción completa y evitar efectos externos duplicados. No conviene reintentar indiscriminadamente errores de validación o restricciones que requieren corregir los datos.
Por último, observa la aplicación en funcionamiento. Una prueba con pocos usuarios puede no revelar los conflictos que aparecerán cuando varias operaciones compitan por los mismos registros.
Conclusión
Elegir entre los niveles de aislamiento en SQL consiste en proteger las reglas de negocio sin imponer coordinación innecesaria. Read Committed evita lecturas sucias; Repeatable Read aporta estabilidad adicional; y Serializable ofrece la garantía más fuerte, con posibles esperas o reintentos.
Antes de cambiar una configuración global, identifica una operación crítica de tu aplicación, describe qué anomalías no puede tolerar y pruébala con transacciones concurrentes en tu motor concreto. Esa evidencia será más útil que elegir un nivel solo por su nombre.

