KriaturasBases de datos para quien empieza

Parte IV · Pulir y proteger

Capítulo 13 — Que no se rompa nada (transacciones, lo esencial)

Al acabar este capítulo sabrás por qué algunas operaciones tienen que ocurrir "todas o ninguna", qué es una transacción y cómo escribir un intercambio de cartas que nunca deje a nadie a medias. Vas a cerrar, por fin, el cabo suelto que dejamos abierto en el capítulo 9: el intercambio seguro.

En el capítulo 12 aprendiste a exprimir tus datos: rankings, estadísticas, cartas más usadas. Tus tablas están bien diseñadas (capítulos 6 a 10) y protegidas con reglas que se cumplen solas (capítulo 11). Parece que ya está todo. Pero hay un peligro que ninguna de esas defensas cubre, y es de los que arruinan un juego de verdad.

Vuelve al capítulo 9. Allí montamos la tabla intercambio: PixelPunk le da un Burbujo a ToxiRana. En el papel es sencillo. Quitarle la carta a uno, dársela al otro. Dos cambios.

¿Y si entre el primer cambio y el segundo se va la luz?

Burbujo desaparece. PixelPunk ya no lo tiene. ToxiRana no lo ha recibido. La carta se ha esfumado en el aire. Y lo peor: nadie la robó, nadie hizo trampas. Simplemente el ordenador se apagó en el peor momento. Este capítulo trata de que eso no pueda pasar jamás.


Dos cambios que son uno solo

Volvamos al trueque, despacio. PixelPunk (jugador 3) le da un Burbujo (carta 3) a ToxiRana (jugador 4). En la tabla coleccion eso significa dos cosas:

  1. A PixelPunk le baja en uno la cantidad de Burbujo que tiene.
  2. A ToxiRana le sube en uno.

En SQL son dos órdenes:

SQL
-- 1) Quitar un Burbujo a PixelPunk (jugador 3)
UPDATE coleccion
SET cantidad = cantidad - 1
WHERE jugador_id = 3 AND carta_id = 3;

-- 2) Dar un Burbujo a ToxiRana (jugador 4)
UPDATE coleccion
SET cantidad = cantidad + 1
WHERE jugador_id = 4 AND carta_id = 3;

Lee las dos órdenes como lo que son: una resta y una suma. Por separado, cada una funciona perfectamente. El problema no está dentro de ninguna de las dos. El problema está en el hueco entre ellas.

Piénsalo. Justo cuando termina la primera orden y antes de que empiece la segunda, el mundo se queda en un estado imposible: el Burbujo no está en ningún sitio. Se lo quitamos a PixelPunk y todavía no se lo dimos a ToxiRana. Durante una fracción de segundo, esa carta no existe.

Si nada interrumpe el proceso, ese estado raro dura un instante y nadie se entera. Pero si algo falla en ese hueco —se va la luz, el programa se cuelga, la conexión se corta—, la base de datos se queda congelada ahí: con la resta hecha y la suma sin hacer. Y eso ya no se arregla solo.

Necesitamos una forma de decirle a la base de datos: "estas dos órdenes son un paquete. O las haces las dos, o no haces ninguna. Nada de quedarte a medias."

Esa forma se llama transacción.

Qué es una transacción

Una transacción es un grupo de operaciones sobre la base de datos —lecturas, cambios o ambos— que se ejecutan como una sola unidad indivisible. O se hacen todas, o no se hace ninguna. No hay término medio.

La idea de "indivisible" es el corazón de todo. Una transacción no se puede partir por la mitad. Es como esos juguetes de una sola pieza: no tienen tornillos que aflojar. O está entero, o no está.

Para usar una transacción le pones una marca de inicio y otra de final a tu grupo de órdenes. Entre medias va todo lo que tiene que ocurrir junto:

SQL
BEGIN;   -- empieza la transacción: "lo que viene es un paquete"

UPDATE coleccion
SET cantidad = cantidad - 1
WHERE jugador_id = 3 AND carta_id = 3;

UPDATE coleccion
SET cantidad = cantidad + 1
WHERE jugador_id = 4 AND carta_id = 3;

COMMIT;  -- confirma: "todo ha ido bien, hazlo definitivo"

Dos palabras nuevas, y son fáciles de recordar si las traduces a algo que ya conoces de cualquier programa:

  • COMMIT es guardar. Le dices a la base de datos: "todo salió bien, haz que estos cambios queden de forma definitiva". Hasta que no haces COMMIT, los cambios están en preparación, como un documento que editas pero todavía no has guardado.
  • ROLLBACK es deshacer. Le dices: "algo ha ido mal, olvida todo lo que he hecho desde BEGIN". La base de datos vuelve exactamente al estado en que estaba antes de empezar, como si la transacción nunca hubiera existido.

¿Y qué pasa con nuestro corte de luz? Aquí está la magia. Si la transacción se interrumpe antes del COMMIT —por lo que sea—, la base de datos hace ROLLBACK automáticamente al recuperarse. Deshace la resta que ya había hecho. Burbujo vuelve a PixelPunk. Nadie pierde nada. El trueque, sencillamente, no llegó a ocurrir, y se intentará otra vez.

Esa es la promesa: con una transacción, el intercambio o se completa entero o se queda como si nunca se hubiera intentado. El estado imposible —la carta que no está en ningún sitio— deja de ser un riesgo. Existe solo dentro de la transacción, donde nadie más lo ve, y desaparece en cuanto la transacción termina, sea confirmándose o deshaciéndose.

Aviso de sintaxis. Como con casi todo en este libro, la idea es universal pero las palabras exactas cambian de un sistema a otro. Aquí usamos BEGIN, COMMIT y ROLLBACK, que entienden la mayoría de bases de datos. Algunas escriben START TRANSACTION en vez de BEGIN, y en muchas cada orden suelta que lanzas ya es su propia mini-transacción si no dices lo contrario. Cuando trabajes con un sistema concreto, mira su manual: el concepto será idéntico, solo cambiará el envoltorio.

Las cuatro garantías: ACID

Cuando los expertos en bases de datos hablan de transacciones, sueltan una palabra que parece sacada de la clase de química: ACID. No te asustes. Es solo un truco para recordar las cuatro garantías que te da una transacción. Cada letra es una promesa. Vamos una a una, con Kriaturas en la cabeza.

A — Atomicidad: todo o nada

Es justo lo que acabamos de ver. La transacción es atómica: indivisible. O se ejecutan todas sus operaciones, o ninguna. No existe "la mitad de un intercambio". La palabra viene de átomo, que en su origen significaba "lo que no se puede cortar". Tu trueque es un átomo: una pieza entera.

C — Consistencia: las reglas se siguen cumpliendo

Aquí enlazamos directamente con el capítulo 11. Si la base de datos estaba en un estado válido antes de la transacción —cumpliendo todas las reglas de integridad que pusiste—, vuelve a estar en un estado válido después.

Fíjate en un detalle bonito: durante la transacción la base de datos puede pasar por momentos raros. En mitad del trueque, el Burbujo no está en ningún sitio. Eso, visto en seco, rompería la cuenta de cartas. Pero no pasa nada, porque ese estado intermedio está escondido dentro de la transacción. Lo que la consistencia garantiza es que al terminar todo cuadra: si entraste cumpliendo las reglas, sales cumpliéndolas. La "consistencia" de ACID es, ni más ni menos, la promesa de que las reglas de integridad del capítulo 11 se siguen respetando.

I — Aislamiento: cada transacción, como si estuviera sola

Las transacciones casi nunca van de una en una. En tu juego con éxito, decenas de ellas ocurren a la vez. El aislamiento garantiza que no se pisen: cada transacción se comporta como si fuera la única que está tocando la base de datos, aunque por dentro el sistema las esté intercalando para ir rápido.

Es como varias personas editando cosas distintas sin enterarse unas de otras. Cada una ve un mundo coherente. Más abajo veremos por qué esto importa tanto.

D — Definitividad (durabilidad): lo guardado, guardado queda

Una vez que haces COMMIT, el resultado es definitivo. Aunque se vaya la luz un segundo después, aunque se reinicie el servidor, ese cambio no se pierde. La base de datos se asegura de dejarlo bien grabado antes de decirte "hecho". Por eso el COMMIT es una promesa seria: cuando la base de datos lo confirma, puedes fiarte de que es para siempre.

Junta las cuatro iniciales —Atomicidad, Consistencia, aislamiento (la I viene del inglés isolation) y Durabilidad— y tienes ACID: las cuatro garantías de que tus datos no se corrompen. No hace falta que memorices la palabreja; sí que entiendas qué te promete cada letra, porque es lo que mantiene tu juego en pie cuando empieza a tener tráfico de verdad.

¿Por qué hace falta el aislamiento? Dos manos en la misma carta

La atomicidad nos salvaba de los cortes a media operación. El aislamiento nos salva de un peligro distinto y más escurridizo: dos cosas pasando a la vez.

Imagina que PixelPunk tiene un único Burbujo, una carta valiosa, y por un fallo del juego consigue ofrecerla en dos trueques a la vez: uno a ToxiRana y otro a LunaVerde. Las dos operaciones arrancan casi en el mismo instante. Las dos miran la colección de PixelPunk y ven "1 Burbujo, disponible". Las dos siguen adelante. Resultado: PixelPunk regala el mismo Burbujo dos veces. De una carta han salido dos. Acabas de duplicar un objeto del juego sin querer, que es justo el tipo de bug que destroza la economía de un juego online.

El aislamiento existe para que esto no ocurra. El gestor de la base de datos (el SGBD, ese programa que está debajo de todo y que conoces desde el capítulo 1) se encarga de ordenar las transacciones que compiten por los mismos datos, de modo que el resultado sea como si hubieran ido una detrás de otra. Cuando dos transacciones quieren tocar la misma fila, el sistema hace esperar a una hasta que la otra termina. Así, la segunda ya ve la realidad actualizada: "0 Burbujo", y el segundo trueque falla, como debe ser.

Lo bueno es que de esto se ocupa el SGBD por ti. Tú no tienes que escribir la lógica de "espera a que el otro termine". Solo tienes que ser consciente de que el peligro existe, y de que no basta con confiar en que dos operaciones simultáneas no se crucen nunca. Se cruzan. Con suficiente tráfico, siempre se cruzan. Por eso envuelves en transacciones lo que tiene que ir junto, y dejas que el sistema haga el resto.

Curiosidad: el abrazo mortal. A veces dos transacciones se quedan esperándose la una a la otra para siempre. La primera tiene bloqueada la carta A y quiere la B; la segunda tiene bloqueada la B y quiere la A. Ninguna suelta lo suyo, ninguna avanza. A esto se le llama, muy dramáticamente, abrazo mortal (deadlock). Que no cunda el pánico: el SGBD lo detecta, sacrifica a una de las dos (le hace ROLLBACK) y deja que la otra siga. Es una curiosidad divertida más que un problema del que tengas que preocuparte ahora.

Resumen

Algunas operaciones, como un intercambio de cartas, son varios cambios que en realidad son uno solo. Si se ejecutan sueltos, un fallo a mitad deja la base de datos en un estado imposible y se pierden datos.

La transacción resuelve esto: agrupa las operaciones entre un BEGIN y un COMMIT y las trata como una unidad indivisible. Si todo va bien, COMMIT lo hace definitivo; si algo falla, ROLLBACK lo deshace entero. Sus cuatro garantías se resumen en ACID: atomicidad (todo o nada), consistencia (se respetan las reglas de integridad del capítulo 11), aislamiento (varias a la vez no se pisan) y durabilidad (lo confirmado perdura). El aislamiento, además, te protege de un peligro extra: dos jugadores tocando los mismos datos a la vez.

Y un mensaje que se repite en este libro: qué operaciones deben ir juntas lo decides tú, porque tú conoces las reglas de tu juego. Ninguna herramienta lo adivina por ti.

Con esto, tus datos están bien diseñados, protegidos y a salvo de cortes. Ya solo queda una pregunta para cuando Kriaturas sea enorme: ¿y si las consultas se vuelven lentas? Cuando tengas un millón de cartas y el ranking tarde una eternidad en salir, necesitarás acelerar. De eso va el próximo capítulo: los índices.