En el mundo de las bases de datos es muy común escuchar hablar del concepto ACID. ACID es un grupo de 4 propiedades que garantizan que las transacciones en las bases de datos se realicen de forma confiable. Veamos en detalle este interesante concepto.

Para empezar a definir ACID en el ámbito de las bases de datos, es necesario comprender el concepto de transacción. En las bases de datos, se denomina transacción a una única operación lógica ("de negocio"). Por ejemplo, es una sola transacción la acción de transferir fondos de una cuenta bancaria a otra, aún cuando involucra varios cambios en distintas tablas.

En 1970, Jim Gray definió las propiedades que necesitaba tener una transacción confiable, y desarrolló tecnologías para automatizarlas. Más tarde, en 1983, Andreas Reuter y Theo Härder crearon el término "ACID" para describir estas 4 propiedades.

ACID, letra por letra

Atomicidad
La Atomicidad requiere que cada transacción sea "todo o nada": si una parte de la transacción falla, todas las operaciones de la transacción fallan, y por lo tanto la base de datos no sufre cambios. Un sistema atómico tiene que garantizar la atomicidad en cualquier operación y situación, incluyendo fallas de alimentación eléctrica, errores y caidas del sistema.
Consistencia
La propiedad de Consistencia se asegura que cualquier transacción llevará a la base de datos de un estado válido a otro estado válido. Cualquier dato que se escriba en la base de datos tiene que ser válido de acuerdo a todas las reglas definidas, incluyendo (pero no limitado a) los constraints, los cascades, los triggers, y cualquier combinación de estos.
aIslamiento
El aislamiento ("Isolation" en inglés) se asegura que la ejecución concurrente de las transacciones resulte en un estado del sistema que se obtendría si estas transacciones fueran ejecutadas una atrás de otra. Cada transacción debe ejecutarse en aislamiento total; por ejemplo, si T1 y T2 se ejecutan concurrentemente, luego cada una debe mantenerse independiente de la otra.
Durabilidad
La durabilidad significa que una vez que se confirmó una transacción (commit), quedará persistida, incluso ante eventos como pérdida de alimentación eléctrica, errores y caidas del sistema. Por ejemplo, en las bases de datos relacionales, una vez que se ejecuta un grupo de sentencias SQL, los resultados tienen que almacenarse inmediatamente (incluso si la base de datos se cae inmediatamente luego).

Sobre la Atomicidad

En una transacción atómica, una serie de operaciones en la base de datos ocurren todas, o no ocurre ninguna. La atomicidad previene que las actualizaciones a la base ocurren de forma parcial, lo cual podría ocasionar mayores problemas que rechazar la transacción entera. En otras palabras, la atomicidad significa indivisibilidad e irreducibilidad.

Usualmente, los sistemas implementan la atomicidad mediante algún mecanismo que indica qué transacción comenzó y cuál finalizó; o manteniendo una copia de los datos antes de que ocurran los cambios. Las bases de datos en general implementan la atomicidad usando algún sistema de logging para seguir los cambios. El sistema sincroniza los logs a medida que resulta necesario una vez que los cambios ocurren con éxito. Luego, el sistema de recuperación de caidas simplemente ignora las entradas incompletas.

En los sistemas de almacenamiento NoSQL con consistencia eventual, la atomicidad se especifica de forma más débil que en los sistemas relacionales, y existe sólo para las filas.

Sobre la Consistencia

La consistencia asegura que los cambios a los valores de una instancia son consistentes con cambios a otros valores de la misma instancia. Una restricción de consistencia es un predicado sobre los datos que funcionan como precondición, postcondición, y condición de transformación en cualquier transacción. El sistema de la base de datos asume que la consistencia se mantiene para cada transacción en las instancias. Por otro lado, asegurar la propiedad de consistencia de la transacción es responsabilidad del usuario.

Sobre el Aislamiento

De las 4 propiedades de ACID en los sistemas de bases de datos, generalmente la propiedad de Aislamiento es la más relajada. Cuando se intenta mantener el más alto nivel de aislamiento, las bases de datos adquieren un bloqueo o implementan un control concurrente multiversión, que puede resultar en pérdida de concurrencia. Esto genera que la aplicación agregue lógica para funcionar correctamente.

La mayoría de las bases de datos ofrecen una cantidad de niveles de aislamiento de transacciones, que controlan el grado de bloqueo que ocurre cuando se seleccionan datos. Para muchas aplicaciones, se pueden construir la mayoría de las transacciones evitando los niveles más altos de aislamiento (por ejemplo, el nivel SERIALIZABLE), y por lo tanto reduciendo la carga en el sistema por bloqueos. El programador tiene que analizar el código de acceso para asegurar que se pueda relajar el nivel de aislamiento sin generar bugs que luego serían dificiles de encontrar. Por otro lado, si se usan niveles más altos de aislamiento, es más probable la aparición de dealocks, lo cual requieren de analisis y la implementación de técnicas de programación para evitarlos.

ANSI/ISO SQL define los siguientes tipos de aislamiento:

Serializable
Este es el nivel más alto de aislamiento. En una base de datos con control concurrente basado en bloqueos, la serialización requiere que los bloqueos de lectura y escritura (que se adquieren en datos seleccionados) sean liberados al finalizar la transacción. También se pueden adquierir bloqueos de rango cuando se realiza un query SELECT con una cláusula WHERE, especialmente cuando se quieren evitar lecturas fantasma. En una base de datos con control concurrente no basado en bloqueos, no se necesitan los bloqueos; sin embargo, si el sistema detecta una colisión de escritura entre muchas transacciones concurrentes, sólo se permite el commit de una de estas transacciones.
Lecturas repetibles (repeatable reads)
En este nivel de aislamiento, un sistema de control de concurrencia basado en bloqueos mantiene los bloqueos de escritura y lectura (que se adquieren en datos seleccionados) hasta el final de la transacción. Sin embargo, no se gestionan los bloqueos de rango, por lo tanto podrían ocurrir lecturas fantasma.
Lecturas sobre commits (read commited)
En este nivel de aislamiento, un sistema de control de concurrencia basado en bloqueos mantiene los bloqueos de escritura (que se adquieren en datos seleccionados) hasta el final de la transacción, pero los bloqueos de lectura se liberan ni bien se realiza la operación de SELECT (y por lo tanto pueden ocurrir lecturas no-repetibles). Igual que el nivel anterior, no se permiten bloqueos de rango.
Lecturas sin commits (read uncommited)
Este es el nivel más bajo de aislamiento. En este nivel se permiten las lecturas sucias, por lo cual una transacción podría ver cambios de otra transacción que todavía tuvieron un commit.

Consecuencia sobre las lecturas

El estandar ANSI/ISO SQL 92 se refiere a tres distintos fenómenos sobre la lectura que ocurren cuando la Transacción 1 lee datos que la Transacción 2 puede haber cambiado.

Lecturas sucias
Las lecturas sucias ("dirty reads") ocurren cuando se permite que una transacción lea una fila que fue modificada por otra transacción que todavía no hizo commit. Las lecturas sin commit funcionan igual que las lecturas no-repetibles; sin embargo, la segunda transacción no necesita haber hecho commit para que el primer query devuelva datos distintos. Lo único que puede prevenirse en el nivel de aislamiento "Lecturas sin commit" es que las actualizaciones aparezcan fuera de órden.
Lecturas no-repetibles
Una lectura no-repetible ocurre cuando, durante el curso de una transacción, se recupera una fila dos veces y el valor de la fila difiere entre las lecturas. En las bases de datos con control de concurrencia basado en bloqueos, el fenómeno de lecturas no-repetibles puede ocurrir cuando no se adquieren bloqueos de lectura al ejecutar un SELECT, o cuando los bloqueos adquieridos en las filas afectadas se liberan ni bien se ejecuta la operación de SELECT.
Lecturas fantasma
Una lectura fantasma ocurre cuando, durante el curso de una transacción, se ejecutan dos queries idénticos, y la colección de filas retornada por el segundo query es diferente al primero. Esto puede ocurrir cuando no se adquieren bloqueos de rango al ejecutar una operación SELECT...WHERE. Las lecturas fantasma son un caso especial de las lecturas no-repetibles, cuando la transacción 1 repite la consulta rango SELECT...WHERE y, entre ambas consultas, la Transacción 2 crea (INSERT) nuevas filas que cumplen con la condición del WHERE.

Sobre la Durabilidad

La durabilidad es la propiedad que garantiza que las transacciones que tuvieron un commit sobrevivan de forma permanente.

La durabilidad puede lograrse almacenando los registros de log de la transacción en un medio de almacenamiento no-volatil antes de aceptar los commit.

En las transacciones distribuidas, todos los participantes deben coordinarse antes de aceptar un commit. Esto se suele realizar mediante el protocolo de "commit en dos fases".

Muchas bases de datos implementan la durabilidad escribiendo las transacciones en un log de transacciones que puede ser reprocesado para recrear el estado del sistema antes de una falla. Una transacción se considera confirmada sólo luego de que haya sido ingresada al log.

Leer más

Inspiración.

"Si tú tienes una manzana y yo tengo una manzana e intercambiamos las manzanas, entonces tanto tú como yo seguiremos teniendo una manzana cada uno. Pero si tú tienes una idea y yo tengo una idea, e intercambiamos las ideas, entonces ambos tendremos dos ideas"

Bernard Shaw