Publicado en

Transacciones SQL y propiedades ACID explicadas

Flujo de una transacción SQL con BEGIN, COMMIT y ROLLBACK y las cuatro letras ACID sobre fondo oscuro

Imagina una transferencia bancaria: el sistema descuenta €100 de la cuenta de origen y los suma a la cuenta de destino. Son dos operaciones que deben ocurrir juntas o no ocurrir ninguna. Si la primera se ejecuta y la segunda falla por cualquier motivo, €100 desaparecen de un lado sin llegar al otro. Las transacciones existen para evitar exactamente ese problema.

¿Qué es una transacción?

Una transacción es un conjunto de operaciones SQL que se ejecutan como una unidad indivisible. O todas tienen efecto, o ninguna lo tiene. No existe un estado intermedio.

Transferencia bancaria que falla a mitad: sin transacción el estado queda inconsistente con dinero perdido, con transacción y ROLLBACK el estado se restaura por completo

En el ejemplo de la transferencia, sin transacción la base de datos queda en un estado inconsistente: la cuenta de origen tiene €100 menos pero la de destino no los ha recibido. Con una transacción, el fallo en el paso 2 provoca que el paso 1 se deshaga automáticamente.

Sintaxis: BEGIN, COMMIT y ROLLBACK

La sintaxis varía ligeramente entre gestores, pero el concepto es el mismo en todos:

-- Iniciar la transacción
BEGIN;
-- Operaciones dentro de la transacción
UPDATE cuentas SET saldo = saldo - 100 WHERE id = 1;
UPDATE cuentas SET saldo = saldo + 100 WHERE id = 2;
-- Confirmar si todo fue bien
COMMIT;
-- O cancelar si algo falló
ROLLBACK;
  • BEGIN: marca el inicio de la transacción. En MySQL también puedes usar START TRANSACTION.
  • COMMIT: confirma todos los cambios y los hace permanentes. Una vez hecho, no hay vuelta atrás.
  • ROLLBACK: cancela todos los cambios realizados desde el BEGIN, dejando la base de datos en el estado previo.

En la mayoría de gestores, cada instrucción SQL individual está en su propia transacción implícita con autocommit activado. Eso significa que si ejecutas un UPDATE sin BEGIN, el cambio se confirma automáticamente. Para agrupar operaciones y tener control explícito, necesitas iniciar la transacción manualmente.

Las propiedades ACID

ACID es el acrónimo que define las cuatro garantías que debe ofrecer cualquier sistema de bases de datos transaccional. No son opcionales: son la base sobre la que se construye la confianza en los datos.

Las cuatro propiedades ACID: Atomicidad, Consistencia, Aislamiento y Durabilidad, cada una con su descripción
  • Atomicidad: la transacción es indivisible. O se ejecutan todas las operaciones o ninguna.
  • Consistencia: la base de datos pasa de un estado válido a otro estado válido. Una transacción no puede dejar los datos en un estado que viole las reglas de integridad definidas.
  • Aislamiento: las transacciones concurrentes no se ven entre sí. Los cambios de una transacción no son visibles para otras hasta que se hace COMMIT.
  • Durabilidad: una vez confirmada con COMMIT, la transacción es permanente. Los datos sobreviven a caídas del servidor gracias al registro de transacciones (transaction log) que escriben en disco antes de confirmar.

SAVEPOINT: puntos de retorno parciales

Dentro de una transacción larga, puedes crear puntos de guardado intermedios con SAVEPOINT. Esto permite hacer rollback hasta un punto concreto sin deshacer toda la transacción:

BEGIN;
UPDATE pedidos SET estado = 'enviado' WHERE id = 101;
SAVEPOINT despues_pedido;
UPDATE stock SET cantidad = cantidad - 1 WHERE id_producto = 5;
-- Si algo falla aquí, volvemos al savepoint sin deshacer el UPDATE de pedidos
ROLLBACK TO SAVEPOINT despues_pedido;
COMMIT;

Los savepoints son útiles en procesos por lotes o cuando quieres manejar errores parciales con más granularidad. No todos los gestores los implementan de la misma forma, pero MySQL y PostgreSQL los soportan completamente.

Niveles de aislamiento

La propiedad de aislamiento tiene un coste: cuanto más estrictamente se aíslan las transacciones entre sí, más espera y bloqueos genera el sistema. Por eso SQL define cuatro niveles de aislamiento, ordenados de menor a mayor restricción:

  • READ UNCOMMITTED: una transacción puede leer cambios no confirmados de otras. Máxima velocidad, mínima consistencia. Rara vez se usa.
  • READ COMMITTED: solo se leen datos ya confirmados. Es el nivel por defecto en PostgreSQL y Oracle.
  • REPEATABLE READ: los datos leídos en una transacción no cambian aunque otra los modifique. Es el nivel por defecto en MySQL.
  • SERIALIZABLE: el nivel más estricto. Las transacciones se comportan como si se ejecutaran en serie, una tras otra. Máxima consistencia, mayor coste en concurrencia.
-- Cambiar el nivel de aislamiento en MySQL / PostgreSQL
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
BEGIN;
-- ... operaciones ...
COMMIT;

Para la mayoría de aplicaciones, el nivel por defecto del gestor es el adecuado. Solo necesitas ajustarlo en casos específicos con requisitos muy estrictos de consistencia o cuando detectas problemas de concurrencia concretos.

Con las transacciones tienes garantías sólidas sobre qué ocurre cuando algo falla. El siguiente nivel es decidir bien cómo estructurar los datos desde el principio para que esas transacciones operen sobre un modelo limpio, sin redundancias ni inconsistencias de diseño. Eso es lo que resuelve la normalización.