Atlasingeniería

Bases de datosMotor y ejecuciónTema 2

Transacciones y ACID

Una transacción convierte varias operaciones en una sola que pasa entera o no pasa. Las cuatro letras de ACID prometen cosas distintas, y la de aislamiento es la que casi nunca está donde uno cree.

Para este tema conviene tener claro:Tablas, claves e integridad referencial

Una transferencia son dos operaciones: restar de una cuenta y sumar a la otra. Si el servidor se apaga en el medio, el dinero no puede desaparecer. Ese requisito, llevado al caso general, es lo que resuelve una transacción.

Atomicidad: todo o nada

Atomicidad: todo o nada. Las operaciones entre el inicio y el COMMIT se aplican juntas; si algo falla, el ROLLBACK deja la base como si nada hubiera pasado.

Se implementa con un registro previo de escritura: antes de tocar los datos, el motor anota en un log qué va a cambiar. Si el sistema cae, al levantar rehace lo confirmado y deshace lo que quedó a medias. El log es también lo que hace posible restaurar a un punto en el tiempo.

Empieza una transferencia de 100 de la cuenta A a la B. Lo primero que se escribe es que arrancó.

1 / 6
Lo importante es el orden: el log se escribe antes que los datos, siempre. Por eso al levantar hay con qué decidir, y la cuenta de origen no queda con cien pesos menos y la de destino sin los cien.

Antes de seguir, predecí

Una transferencia descuenta de una cuenta, y el proceso muere antes de acreditar en la otra. ¿Qué queda?

Consistencia y durabilidad

Consistencia: una transacción lleva la base de un estado válido a otro, respetando claves, foráneas y CHECK. Es la letra más discutida, porque en buena medida depende de que las restricciones estén declaradas: el motor hace cumplir lo que se le dijo.

Durabilidad: una vez confirmada, la transacción sobrevive a un corte de energía. Se logra escribiendo el log a disco antes de responder, y de ahí sale la tensión con el rendimiento: hacer fsync en cada commit es lento, y la mayoría de los motores ofrecen aflojar esa garantía a cambio de velocidad. Es una decisión, no un detalle de configuración.

Aislamiento, que es la letra difícil

Aislamiento: el resultado de ejecutar transacciones concurrentes debería ser el mismo que ejecutarlas una detrás de otra. Ésa es la definición fuerte, y es la que casi ningún sistema aplica por defecto, porque cuesta cara.

En su lugar hay niveles, y elegir uno es elegir qué anomalías se aceptan. La mayoría de las bases viene configurada en un nivel intermedio, y muchísimo código asume el nivel más fuerte sin saberlo.

Las tres anomalías clásicas

Las anomalías clásicas son tres. Lectura sucia: ver datos de una transacción que todavía no confirmó. Lectura no repetible: leer dos veces la misma fila dentro de una transacción y obtener valores distintos. Lectura fantasma: repetir una consulta por rango y encontrar filas nuevas.

A eso se agrega una cuarta que no está en la lista original y es la que más daño hace en la práctica: la actualización perdida. Dos transacciones leen el mismo saldo, cada una calcula el nuevo valor y escribe; la segunda pisa a la primera y un movimiento desaparece.

Los cuatro niveles estándar

Los niveles estándar son cuatro. READ UNCOMMITTED permite todo. READ COMMITTED evita las lecturas sucias y es el default de PostgreSQL y Oracle. REPEATABLE READ además garantiza que lo leído no cambie, y es el default de MySQL. SERIALIZABLE no admite ninguna anomalía.

Conviene saber el default del motor que se usa, porque el código se escribe implícitamente contra uno. Y hay un detalle importante: en READ COMMITTED, leer un valor, calcular y escribirlo no es seguro. Hay que releer con bloqueo —SELECT ... FOR UPDATE—, hacer la actualización en una sola sentencia, o usar control optimista con un número de versión.

Dos reglas prácticas

Dos reglas prácticas. Las transacciones cortas: cuanto más tiempo abiertas, más bloqueos retienen y más conflictos generan. Nunca abarcar una llamada a un servicio externo dentro de una transacción abierta.

Y asumir que pueden fallar. Un interbloqueo hace que el motor cancele una de las dos transacciones, y eso no es un error excepcional: es el mecanismo funcionando. El código tiene que poder reintentar, y para eso la operación tiene que ser segura de repetir.

Una transferencia, escrita bien

BEGIN;

-- Bloquea las dos filas, siempre en el mismo orden para no trabar con otra transacción
SELECT balance FROM accounts WHERE id IN (1, 2) ORDER BY id FOR UPDATE;

UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 100 WHERE id = 2;

-- La regla de negocio, verificada dentro de la transacción
-- (con un CHECK (balance >= 0) el motor lo hace solo)

COMMIT;

Tres decisiones en cinco líneas. La primera: FOR UPDATE bloquea las filas para que ninguna otra transacción las lea con intención de modificarlas hasta el COMMIT. Sin eso, dos transferencias simultáneas pueden leer el mismo saldo y las dos restar sobre el valor viejo.

La segunda: el ORDER BY id no es cosmético. Si una transacción toma la 1 y después la 2 mientras otra toma la 2 y después la 1, quedan trabadas para siempre. Tomar los bloqueos siempre en el mismo orden global es la forma más barata de hacer imposible un interbloqueo.

La tercera: escribir balance = balance - 100 en vez de leer en la aplicación y escribir el resultado. La resta la hace el motor sobre el valor actual, así que no hay ventana entre la lectura y la escritura.

Lo que ACID cuesta, y qué se afloja

LetraQué garantizaCon qué se paga
Atomicidadtodo o nadaun registro previo de escritura
Consistencialas restricciones se cumplenverificarlas en cada escritura
Aislamientocomo si corriera solabloqueos y espera, o versiones y memoria
Durabilidadsobrevive a un corteun fsync antes de responder
La durabilidad es la que más se afloja en la práctica, y casi nunca de forma explícita: varios motores traen por defecto una configuración que confirma antes de que el dato esté realmente en disco.

Cierre

Atomicidad por el log, durabilidad por escribirlo antes de confirmar, consistencia por las restricciones declaradas. El aislamiento es el que viene recortado por defecto: conviene saber en qué nivel corre la base y no dar por sentado que leer, calcular y escribir es seguro.

Autoevaluación

¿Lo entendiste?

¿Cómo se implementa la atomicidad?
La C de ACID es la más discutida. ¿De qué depende la consistencia?
¿Por qué la durabilidad tensiona con el rendimiento?
Una transacción hizo tres UPDATE y falla el cuarto. ¿Qué queda en la base?