El dinero no se duplica · Episodio 4 de 7
Dos Bizum a la vez
Laura lleva 1.200 € de Bizum hoy. Desde el móvil envía 600 € y, en el mismo segundo, desde la tableta, 500 €. Las dos peticiones miran el límite, las dos ven 1.200 € y las dos pasan: 2.300 €.
El punto de partida
En Las áreas del banco, el límite diario de Laura se convirtió en un agregado: el grupo de datos que se cambia de una vez para que la regla de los 2.000 € se cumpla siempre. La definición prometía algo concreto: mientras se cambia, nadie más puede tocarlo.
Esa promesa no la cumple el diagrama. La cumple la base de datos, con sus transacciones, y hasta dónde la cumple depende de una opción que casi nadie mira: el nivel de aislamiento.
Leer, comprobar, escribir
Para cumplir la regla, cada Bizum hace tres cosas: lee cuánto lleva Laura hoy, comprueba que el nuevo cabe y escribe. Si dos Bizum lo hacen a la vez, sus pasos se entrelazan: los dos leen antes de que ninguno escriba.
Que eso pase no es raro. Laura tiene dos dispositivos vinculados; la app reintenta; Operaciones corre en varios servidores. «A la vez» es cada día.
En la simulación, cada transacción tiene su línea de tiempo: T1 arriba, T2 abajo. En medio está el dato que comparten. Elige cómo se guarda el dato y qué nivel de aislamiento usa la base de datos, y lanza las dos transacciones. Prueba las seis combinaciones.
Qué ha pasado
El entrelazado es siempre el mismo. Lo que cambia es qué deja pasar la base de datos, y depende de dos cosas a la vez:
- Con un contador, las dos transacciones escriben el mismo dato. En read committed, la segunda escritura pisa a la primera (lost update): el contador dice 1.700 € y han salido 2.300 €. En snapshot o serializable, la base de datos detecta que T2 va a escribir sobre una versión que ya no es la que leyó, la aborta, y al reintentarla la regla la rechaza.
- Con una fila por Bizum, cada transacción añade su propia fila y nadie escribe sobre lo de nadie. Snapshot no ve ningún choque y deja pasar las dos (write skew): cada una comprobó la regla con su foto y, juntas, la rompen. Solo serializable lo detecta.
La lección incómoda es que el mismo nivel de aislamiento protege un diseño y no otro. Guardar los Bizum como filas que nunca se modifican es un buen diseño (no se pierde historia), y precisamente por eso snapshot deja de bastar.
El recibo y el Bizum
El día de cobro, el recibo de la luz (90 €) llega a la cuenta de Laura justo cuando ella envía un Bizum de 50 €. Tiene 100 € disponibles: cabe uno de los dos, no los dos. Según las reglas de las domiciliaciones, si el saldo no cubre el recibo, se devuelve.
Elige el caso «Recibo y Bizum» en la simulación. El saldo de una cuenta es la suma de sus movimientos, y los movimientos nunca se modifican: es exactamente el caso de las filas. Con snapshot, los dos pasan y la cuenta queda en −40 €. Con un saldo guardado y read committed, es peor: el saldo dice 50 € y han salido 140 €.
El nivel más estricto no es gratis
Si serializable lo arregla todo, ¿por qué no usarlo siempre? Porque aborta más transacciones, y cada una hay que reintentarla. Con mucho tráfico sobre los mismos datos, los reintentos se acumulan. Y muchas bases de datos no lo usan por defecto: lo normal es read committed. Hay que saber qué nivel se tiene, y qué anomalías deja pasar con la forma en que se guardan los datos.
Hay otra salida: no dejar que dos transacciones lleguen a leer a la vez. Es decir, bloquear. Pero bloquear tiene su propio precio, y lo veremos con la cuenta de casa un día 1 de mes.
Para llevarse
- «A la vez» es cada día. Leer, comprobar y escribir se entrelazan, y la regla se puede romper sin que ninguna transacción se equivoque.
- El nivel de aislamiento decide qué anomalías deja pasar la base de datos: lost update en read committed, write skew hasta snapshot.
- La forma en que se guarda un dato decide qué anomalías le afectan. El nivel más estricto lo protege todo, a cambio de abortar y reintentar más.