Aislamiento (ACID)
# Aislamiento (ACID)

En bases de datos, el aislamiento es una propiedad que define cómo y cuándo los cambios producidos por una operación se hacen visibles para las demás operaciones concurrentes. Aislamiento es una de las 4 propiedades ACID (Atomicidad, Consistencia, Aislamiento, Durabilidad) aplicables a una base de datos transaccional.

## Niveles de aislamiento

De las cuatro propiedades ACID de un Sistema de gestión de bases de datos relacionales (SGBDR) la de aislamiento es la que más frecuentemente se relaja. Para obtener el mayor nivel de aislamiento, un SGBDR generalmente hace un bloqueo de los datos o implementa un Control de concurrencia mediante versiones múltiples (MVCC), lo que puede resultar en una pérdida de concurrencia. Por ello se necesita añadir lógica adicional al programa que accede a los datos para su funcionamiento correcto.

La mayor parte de los SGBDR ofrecen unos ciertos niveles de aislamiento que controlan el grado de bloqueo durante el acceso a los datos. Para la mayor parte de aplicaciones, el acceso a los datos se puede realizar de modo que se eviten altos niveles de aislamiento (i.e. nivel SERIALIZABLE), reduciendo así la sobrecarga debida a la necesidad de bloqueos por el sistema. El programador debe analizar detenidamente el código que accede a la base de datos para asegurarse de que el descenso del nivel de aislamiento que ofrece el SGBD no produce errores en el programa. Recíprocamente, si se usan altos niveles de aislamiento la posibilidad de bloqueo aumenta, lo que también requiere análisis cuidadoso del código.

Los niveles de aislamiento están definidos por ANSI/ISO SQL, y se listan a continuación.

### Serializable

Este es el nivel de aislamiento más alto. Especifica que todas las transacciones ocurran de modo aislado, o dicho de otro modo, como si todas las transacciones se ejecutaran de modo serie (una tras otra). La sensación de ejecución simultánea de dos o más transacciones que perciben los usuarios sería una ilusión producida por el SGBD.

Si el SGBDR hace una implementación basada en bloqueos, la serialización requiere que los bloques de lectura y escritura se liberen al final de la transacción. Del mismo modo deben realizarse bloqueos de rango -sobre los datos seleccionados con SELECT usando WHERE- para evitar el efecto de las lecturas fantasma (ver más abajo).

Cuando se hace una implementación no basada en bloqueos, si el SGBDR detecta una colisión de escritura entre transacciones solo a una de ellas se le autoriza cometer.

### Lecturas repetibles (Repeatable reads)

En este nivel de aislamiento, un SGBDR que implemente el control de concurrencia basado en bloqueos mantiene los bloqueos de lectura y escritura -de los datos seleccionados- hasta el final de la transacción. Sin embargo, no se gestionan los bloqueos de rango, por lo que las lecturas fantasma pueden ocurrir (ver más abajo).

### Lecturas comprometidas (Read committed)

En este nivel de aislamiento, un SGBDR que implemente el control de concurrencia basado en bloqueos mantiene los bloqueos de escritura -de los datos seleccionados- hasta el final de la transacción, mientras que los bloqueos de lectura se cancelan tan pronto como acaba la operación de SELECT (por lo que el efecto de las lecturas no repetibles puede ocurrir, como se explica más abajo). Al igual ocurría en el nivel anterior, no se gestionan los bloqueos de rango.

### Lecturas no comprometidas (Read uncommitted)

Este es el menor nivel de aislamiento. En él se permiten las lecturas sucias (ver más abajo), por lo que una transacción puede ver cambios no cometidos aún por otra transacción.

## Nivel de aislamiento por defecto

El nivel de aislamiento por defecto de distintos SGBDR varía ampliamente. La mayoría de bases de datos que gestionan transacciones permiten al usuario establecer cualquier nivel de aislamiento. Algunos SGBDR requieren sintaxis especial cuando se realiza una operación SELECT que efectúa bloqueos (e.g. SELECT ... FOR UPDATE para bloquear para escritura aquellas filas accedidas).

Sin embargo, la definición anterior ha sido criticada en el artículo A Critique of ANSI SQL Isolation Levels por ambigua, y por no reflejar de modo preciso el aislamiento proporcionado por muchas bases de datos:

    El artículo muestra las debilidades en la definición de niveles de aislamiento. Los tres efectos ANSI son ambiguos. Incluso su interpretación más amplia no excluye el comportamiento anormal. Esto lleva a resultados no intuitivos. En concreto, los niveles de aislamiento basados en bloqueos tienen distintas características que sus equivalentes ANSI. Esto desconcierta ya que los sistemas comerciales típicamente usan bloqueos. Además, los efectos ANSI no distinguen entre los múltiples niveles de aislamiento de los sistemas comerciales.

Hay también otras críticas sobre las definiciones de aislamiento SQL de ANSI, en cuanto a que incita a los implementadores a realizar "trabajos sucios":

    ... se basa de manera subrepticia en que se usa un esquema de bloqueo para control de concurrencia, de modo opuesto a un sistema de concurrencia optimista o de versión múltiple (MVCC). Esto implica que la semántica propuesta está mal definida.

## Efectos en lectura

El estándar ANSI/ISO SQL 92 se refiere a tres efectos de lectura diferentes cuando la transacción 1 lee datos que podría haber cambiado la transacción 2.

En los siguientes ejemplos se ejecutan dos transacciones. La primera ejecuta la consulta 1. Entonces una segunda transacción ejecuta la consulta 2 y la comete. Por último, la primera transacción ejecuta la consulta 1 de nuevo.

Las consultas usan esta tabla:

usuarios :

| id | nombre | edad |
| - | - | - |
| 1 | José | 20 |
| 2 | Juana | 25 |

### Lecturas sucias

Una lectura sucia ocurre cuando se le permite a una transacción la lectura de una fila que ha sido modificada por otra transacción concurrente pero todavía no ha sido cometida.

Las lecturas sucias funcionan de modo similar a las lecturas no repetibles; sin embargo la segunda transacción no necesita ser cometida para que la primera dé un resultado diferente. Lo único que se puede prevenir en el nivel de aislamiento LECTURAS NO COMETIDAS es que las actualizaciones aparezcan en desorden en el resultado; esto es, que las primeras actualizaciones siempre aparecerán antes que las actualizaciones posteriores.

En el ejemplo, la transacción 2 cambia una fila, pero no comete los cambios. La transacción 1 entonces lee los datos sin cometer. Si ahora la transacción 2 deshace sus cambios (ya leídos por la transacción 1) o realiza otros cambios, entonces los datos que ha recuperado la transacción 1 serán erróneos.

| Transacción 1 | Transacción 2 |
| - | - |
| /* Query 1 */ SELECT edad FROM usuarios WHERE id = 1; /* leerá 20 */ | |
| | /* Consulta 2 */ UPDATE usuarios SET edad = 21 WHERE id = 1; /* No se hace commit */ |
| /* Query 1 */ SELECT edad FROM usuarios WHERE id = 1; /* leerá 21 */ | |
| | ROLLBACK; /* LECTURA SUCIA basada en bloqueo */ |

Pero no existe ningún usuario que tenga la edad de 21, pues la Transacción 2 nunca cometió los cambios.

### Lecturas no repetibles

Una lectura no repetible ocurre cuando en el curso de una transacción una fila se lee dos veces y los valores no coinciden.

El efecto de lecturas no repetible puede ocurrir en una implementación de concurrencia mediante bloqueos cuando no se efectúan éstos al hacer un SELECT, o cuando los bloqueos se liberan nada más terminar la operación SELECT. Cuando se usa el método MVCC, las lecturas no repetibles pueden aparecer cuando se relaja el requisito de que al cometer una transacción afectada por un conflicto esta deba deshacerse.

| Transacción 1 | Transacción 2 |
| - | - |
| /* Consulta 1 */ SELECT * FROM usuarios WHERE id = 1; | |
| | /* Consulta 2 */ UPDATE usuarios SET edad = 21 WHERE id = 1; COMMIT; /* en MVCC o READ COMMITTED basado en bloqueos */ |
| /* Consulta 1 */ SELECT * FROM usuarios WHERE id = 1; COMMIT; /* REPEATABLE READ basado en bloqueos */ |

En este ejemplo, la transacción 2 comete correctamente, lo que significa que sus cambios a la fila con id 1 deberían hacerse visibles. Sin embargo, la transacción 1 ya ha leído un valor distinto para edad en esa fila. En los niveles de aislamiento SERIALIZABLE y REPEATABLE READ, el SGBDR debería devolver el valor antiguo. En los niveles READ COMMITTED y READ UNCOMMITTED, el SGBDR debería devolver el valor nuevo; esto es una lectura no repetible.

Hay dos estrategias básicas para prevenir las lecturas no repetibles. La primera consiste en retrasar la ejecución de la transacción 2 hasta que la transacción 1 haya cometido o haya deshecho. Este método se usa con bloqueos, y es equivalente a la ejecución en serie T1, T2. La ejecución en serie no muestra efectos de lecturas no repetibles.

La otra estrategia, la usada en MVCC, se permite cometer a la transacción 2 primero, lo que permite mejor concurrencia. Si embargo, la transacción 1, que comenzó antes que la transacción 2, debe continuar operando sobre una versión anterior de la base de datos —una instantánea del momento en que empezó. Cuando la transacción 1 intenta cometer, el SGBDR verifica si el resultado de cometer la transacción 1 sería equivalente a la ejecución serie T1, T2. Si fuera así la transacción 1 podría proceder. Si no fuera así, la transacción 1 fallaría y debería deshacerse.

Usando control de concurencia mediante bloqueos, al nivel de aislamiento REPEATABLE READ, la fila con id 1 debería bloquearse, bloqueando de ese modo la consulta 2 hasta que la transacción 1 se cometa o deshaga. En el modo READ COMMITTED, la segunda vez que se ejecute la consulta 1, la edad habrá cambiado.

Usando MVCC, al nivel de aislamiento SERIALIZABLE, ambas consultas SELECT ven una instantánea de la base de datos tomada al comienzo de la transacción 1. Por ello devuelven los mismo datos. Sin embargo, si la transacción 1 intenta entonces actualizar esa misma fila, ocurriría un fallo de serialización y la transacción 1 estaría forzada a deshacerse.

Al nivel de aislamiento READ COMMITTED, cada consulta ve una instantánea de la base de datos tomada al principio de la consulta. Por ello, cada una ve datos distintos en la fila actualizada. No puede producirse fallo de serialización en este modo (ya que no se hace promesa de serializabilidad), y la transacción 1 no deberá reintentarse.

### Lecturas fantasma

Una lectura fantasma ocurre cuando, durante una transacción, se ejecutan dos consultas idénticas, y los resultados de la segunda no son iguales a los de la primera.

Esto puede ocurrir cuando no se realizan bloqueos de rango al realizar una operación SELECT ... WHERE.

La anomalía de las lecturas fantasma es una caso particular de las lecturas no repetibles cuando la transacción 1 repite una consulta acotada en rango SELECT ... WHERE y, entre ambas operaciones la transacción 2 crea (i.e. INSERT) nuevas filas (en la misma tabla) que entran dentro de esa cláusula WHERE.

| Transacción 1 | Transacción 2 |
| - | - |
| /* Consulta 1 */ SELECT * FROM usuarios WHERE edad BETWEEN 10 AND 30; | |
| | /* Consulta 2 */ INSERT INTO usuarios VALUES ( 3, 'Mica', 27 ); COMMIT; |
| /* Consulta 1 */ SELECT * FROM usuarios WHERE edad BETWEEN 10 AND 30; | |

Nótese que la transacción 1 ejecuta la misma consulta dos veces. Si se mantuviera el mayor nivel de aislamiento, los resultados de ambas consultas coincidirían (QUE ES LO QUE SUCEDE), de hecho es lo que se pide a una base de datos operando al nivel de aislamiento SERIALIZABLE. Sin embargo, a niveles de aislamiento menores, pueden obtenerse resultados distintos.

Al nivel de aislamiento SERIALIZABLE, la consulta 1 bloquearía todos los registros con edades comprendidas entre 10 y 30, de modo que la consulta 2 quedaría bloqueada hasta que se cometiera la transacción 1. En modo REPEATABLE READ, el rango de 10 a 30 no se bloquearía, permitiendo la inserción de modo que la segunda ejecución de la consulta 1 sí incluirá la nueva fila en sus resultados.

## Niveles de aislamiento, efectos de lectura y bloqueos

### Niveles de aislamiento vs Efectos de lectura

| Nivel de aislamiento | Lectura sucia | Lectura no repetibles | Lectura fantasma |
| - | - | - | - |
| Read Uncommitted | puede ocurrir | puede ocurrir | puede ocurrir |
| Read Committed | - | puede ocurrir | puede ocurrir |
| Repeatable Read | - | - | puede ocurrir |
| Serializable | - | - | - |

"puede ocurrir" significa que el nivel de aislamiento sufre ese efecto, mientras que "-" significa que no lo sufre.

### Niveles de aislamiento vs Bloqueos

| Nivel de aislamiento | Bloqueo de escritura | Bloqueo de lectura | Bloqueo de rango |
| - | - | - | - |
| Read Uncommitted | - | - | - |
| Read Committed | V | - | - |
| Repeatable Read | V | V | - |
| Serializable | V | V | V |

"V" indica que el método bloquea esa operación, manteniéndolo hasta el final de la transacción.

Nota: Las operaciones de lectura (i.e. SELECT) pueden efectuar bloqueos (compartidos) en el nivel Read Committed, pero se liberan inmediatamente tras la operación de lectura.

