Todo o nada
Dos líneas que tienen que ir juntas
Ana le pasa 100 € a Luis. Los dos tienen cuenta en BCPS Bank, así que no hay otro banco de por medio: Cuentas quita 100 € de la cuenta de Ana y pone 100 € en la de Luis. Son dos líneas. ¿Qué pasa si se va la luz justo entre una y otra?
En la serie de eventos lo despachábamos con una frase: «Cuentas lo hace en una sola transacción». Todo lo interesante pasaba fuera, entre servicios. Hoy abrimos esa caja. Qué es exactamente una transacción, qué garantiza cada una de las cuatro letras de ACID y, sobre todo, qué fallo evita cada una.
Porque ACID no se entiende con definiciones. Se entiende viendo lo que pasa sin ella.
La transferencia, a cámara lenta
Ana tiene 500 € y Luis 200. A la izquierda, Cuentas hace la transferencia sin transacción: cada sentencia se confirma sola, en cuanto termina. A la derecha, dentro de BEGIN … COMMIT. El tiempo corre hacia abajo; en medio, la columna gris es lo que está confirmado en la base de datos después de cada paso. Primero transfiere sin fallos; luego elige qué pasa y vuelve a transferir.
Qué ha pasado
A de atomicidad: todo o nada
Sin transacción, cuando la luz se va entre el cargo y el abono, 100 € desaparecen.probar en el laboratorio El cargo a Ana ya estaba confirmado; el abono a Luis no llegó a existir. Nadie tiene ese dinero, y nadie se va a dar cuenta hasta que Ana mire su extracto.
Con transacción, el cargo no estaba confirmado cuando llegó la caída. Al arrancar, la base de datos descarta todo lo que no llegó al COMMIT: Ana sigue con 500 €, Luis con 200, y Ana recibe un error y puede volver a intentarlo. Eso es la atomicidad: las sentencias de una transacción ocurren todas o ninguna. No existe el «a medias».
Es exactamente lo que la serie de DDD llamaba una excepción deliberada: la transferencia interna toca dos cuentas, dos agregados, en una sola transacción. BCPS Bank lo decidió así, y lo dejó escrito, porque la regla que importa, «el dinero nunca queda a medias», abarca las dos cuentas a la vez.
C de consistencia: las reglas se cumplen antes y después
La base de datos de Cuentas tiene una regla: el saldo de una cuenta no puede ser negativo (CHECK (saldo >= 0)). Ana transfiere 500 €, que es justo lo que tiene, y el banco cobra 2 € de comisión.probar en el laboratorio La tercera sentencia rompe la regla y la base de datos la rechaza.
Sin transacción, la regla solo frena esa sentencia: el cargo de 500 € y el abono ya estaban confirmados. A Ana le llega un error, pero Luis tiene los 500 € de una transferencia que ha fallado. Con transacción, el fallo deshace la transacción entera. La consistencia de ACID es eso: una transacción lleva la base de datos de un estado que cumple todas sus reglas a otro que también las cumple, y nunca deja ver uno intermedio que las rompa.
Ojo con la palabra: esta «consistencia» no es la del teorema CAP (episodio 5), que habla de réplicas. Es la misma palabra para dos cosas distintas, y se confunden a menudo.
D de durabilidad: lo confirmado no se pierde
Si la luz se va después del COMMIT, lo confirmado sobrevive, en los dos paneles.probar en el laboratorio Fíjate en la tarjeta del WAL: antes de responder «confirmado», la base de datos ha apuntado cada cambio y el propio COMMIT en un registro en disco. Al arrancar, lo lee y rehace lo confirmado. Lo que no llegó a confirmarse aparece tachado: se descarta.
Eso es la durabilidad: una vez que Ana ve «Transferencia hecha», ninguna caída la deshace. Por eso un COMMIT tarda lo que tarda el disco en guardar ese registro de verdad, y por eso las bases de datos que prometen durabilidad no responden antes.
Lo que la transacción no puede deshacer
Ahora el aviso a Ana se manda dentro de la transacción, antes del COMMIT, y la luz se va justo después.probar en el laboratorio Los saldos cuadran: la base de datos ha deshecho la transferencia. Pero el SMS ya había salido, y Ana tiene en el móvil un «Transferencia hecha» que es mentira.
Una transacción solo abarca lo que hay dentro de la base de datos. Un SMS, un correo, una llamada a Antifraude o el envío a otro banco quedan fuera: un ROLLBACK no los deshace. Por eso Notificacionesver en el mapa se entera por eventos, después de confirmar, y por eso existe el patrón outbox: escribir el aviso en una tabla dentro de la misma transacción y mandarlo después. Está en el episodio 4 de la serie de eventos.
I de aislamiento: lo que ven los demás
Queda una letra. Mientras la transferencia va por la mitad, Reportingver en el mapa suma el dinero de Ana y Luis.probar en el laboratorio Sin transacción, ve 600 €: el dinero ya ha salido de Ana y aún no ha llegado a Luis. Con transacción, Reporting no ve el cargo, porque no está confirmado: suma 700 €, que es la verdad de ese momento.
Eso es el aislamiento: cada transacción se comporta como si estuviera sola. Pero aquí Reporting solo lee. ¿Qué pasa si las dos transacciones escriben la misma cuenta, cada una convencida de que hay saldo? Ahí el aislamiento deja de ser un sí o un no, y aparece una palabra nueva: nivel. Es el siguiente episodio.
Criterio
Cada letra, el fallo que evita y lo que cuesta:
| Letra | Evita | Cómo | Precio |
|---|---|---|---|
| A · atomicidad | Operaciones a medias | Lo no confirmado se descarta entero | Hay que agrupar bien: una transacción demasiado grande bloquea más y falla más |
| C · consistencia | Estados que rompen las reglas | Restricciones (CHECK, claves) comprobadas dentro de la transacción | Solo protege las reglas que se le dicen a la base de datos |
| I · aislamiento | Ver o pisar el trabajo a medias de otro | Snapshots y bloqueos (episodios 2 a 4) | Esperas y reintentos; por eso hay niveles |
| D · durabilidad | Perder lo confirmado | WAL en disco antes de responder | Cada COMMIT espera al disco |
¿Cuándo no hace falta una transacción? Cuando la operación es una sola sentencia: un UPDATE ya es atómico por sí mismo. Y cuando lo que hay que hacer junto cruza servicios o bases de datos distintas, porque ahí una transacción de base de datos no llega: eso es una saga, con sus compensaciones. La regla práctica: todo lo que tenga que cumplirse junto, dentro de la misma base de datos, en una transacción; y nada que no se pueda deshacer, dentro.
En el mundo real
PostgreSQL, MySQL (con InnoDB), Oracle y SQL Server son transaccionales y usan un WAL o algo equivalente (el redo log de InnoDB y Oracle, el transaction log de SQL Server). Todas hacen autocommit por defecto: si no se abre una transacción, cada sentencia es la suya. Muchos ORM abren una por petición sin que se note, y la sorpresa llega el día que alguien parte una operación en dos llamadas.
La durabilidad es configurable, y a veces se relaja a propósito: PostgreSQL tiene synchronous_commit = off y MySQL innodb_flush_log_at_trx_commit = 2, que responden antes de que el disco confirme y pueden perder el último segundo tras una caída. Redis, por defecto, puede perder hasta un segundo de escrituras con su AOF. Para un banco no vale; para un contador de visitas, sí.
Muchas bases de datos NoSQL nacieron sin transacciones de varias filas y las han ido añadiendo: MongoDB desde la 4.0, DynamoDB con TransactWriteItems. Suelen ser más caras y con más límites que las de una sola fila, y el consejo sigue siendo diseñar para no necesitarlas a menudo.
Siguiente episodio
Ana y Luis comparten una cuenta, la de gastos. Un sábado, cada uno saca dinero en un cajero distinto, a la vez. Las dos transacciones leen el saldo, las dos ven que hay suficiente, las dos sacan. ¿Cuánto queda en la cuenta?
Siguiente · Episodio 2 Dos cajeros a la vez Niveles de aislamiento, el lost update y el write skew. Tú decides el orden de los pasos.