Aprende SQL

Transacciones: BEGIN, COMMIT y ROLLBACK

Imagina que estás transfiriendo dinero de una cuenta bancaria a otra. Son dos operaciones: restar de una cuenta y sumar a la otra. ¿Qué pasa si la primera se ejecuta pero la segunda falla? Tendrías dinero que desaparece. Las transacciones existen para evitar exactamente eso.

BEGIN
Se abre la transacción
Operaciones agrupadas
INSERT INTO cuentas ...
UPDATE cuentas SET ...
DELETE FROM logs ...
¿Todo correcto?
COMMIT
Cambios guardados permanentemente
No
ROLLBACK
Todo vuelve al estado original

¿Qué es una transacción?

Una transacción es un grupo de operaciones SQL que se ejecutan como una unidad indivisible. O se ejecutan todas correctamente, o no se ejecuta ninguna.

Este principio se conoce como atomicidad, y es la primera letra de las propiedades ACID que garantizan la fiabilidad de las bases de datos:

PropiedadSignificado
AtomicidadTodo o nada. Si una operación falla, se deshacen todas.
ConsistenciaLa base de datos pasa de un estado válido a otro estado válido.
IsolamientoLas transacciones concurrentes no se interfieren entre sí.
DurabilidadUna vez confirmada, la transacción sobrevive a fallos del sistema.

Sintaxis básica

BEGIN TRANSACTION;

-- Operaciones que forman la transacción
UPDATE cuentas SET saldo = saldo - 100 WHERE id = 1;
UPDATE cuentas SET saldo = saldo + 100 WHERE id = 2;

COMMIT;

BEGIN TRANSACTION (o simplemente BEGIN) abre la transacción. Todas las operaciones que escribas después quedan en espera hasta que las confirmes con COMMIT o las deshagas con ROLLBACK.

COMMIT: confirmar los cambios

COMMIT guarda permanentemente todos los cambios realizados desde el BEGIN. Una vez ejecutado, no hay vuelta atrás.

BEGIN TRANSACTION;
INSERT INTO pedidos (cliente_id, total) VALUES (5, 59.99);
UPDATE clientes SET total_compras = total_compras + 59.99 WHERE id = 5;
COMMIT;

Si ambas operaciones se ejecutan sin error, COMMIT las graba definitivamente.

ROLLBACK: deshacer los cambios

ROLLBACK deshace todos los cambios realizados desde el BEGIN. La base de datos vuelve exactamente al estado anterior a la transacción.

BEGIN TRANSACTION;
DELETE FROM videogames WHERE developer = 'Valve';
-- ¡Ups! No queríamos borrar todos los juegos de Valve
ROLLBACK;
-- La tabla queda como estaba antes del DELETE

Esto es extremadamente útil como red de seguridad cuando ejecutas operaciones peligrosas.

Comportamiento por defecto: autocommit

Sin una transacción explícita, cada sentencia SQL se ejecuta en su propia transacción implícita que se confirma automáticamente. Esto se llama autocommit.

-- Esto se confirma automáticamente
DELETE FROM videogames WHERE id = 1;
-- El registro ya se borró permanentemente, no hay ROLLBACK posible

Cuando usas BEGIN, desactivas el autocommit hasta que ejecutes COMMIT o ROLLBACK.

Ejemplo clásico: transferencia bancaria

Este es el ejemplo más importante para entender por qué las transacciones existen:

BEGIN TRANSACTION;

-- Paso 1: Restar de la cuenta origen
UPDATE cuentas SET saldo = saldo - 500
WHERE id = 1 AND saldo >= 500;

-- Paso 2: Sumar a la cuenta destino
UPDATE cuentas SET saldo = saldo + 500
WHERE id = 2;

COMMIT;

Sin transacción, si el sistema falla entre el paso 1 y el paso 2, el dinero desaparece. Con transacción, si algo falla, ROLLBACK asegura que ninguno de los dos cambios se aplica.

Transacciones en SQLite

SQLite soporta transacciones completamente, con algunas particularidades:

  • Puedes usar BEGIN TRANSACTION o simplemente BEGIN.
  • SQLite también acepta END como sinónimo de COMMIT.
  • SQLite usa bloqueo a nivel de archivo: solo una escritura a la vez (por eso no es ideal para alta concurrencia).
-- Las tres formas son equivalentes en SQLite:
BEGIN TRANSACTION;
BEGIN;

-- Y para confirmar:
COMMIT;
END;

Cuándo usar transacciones

Usa transacciones siempre que:

  • Varias operaciones deben ser atómicas: transferencias, creación de entidades con relaciones, etc.
  • Necesitas una red de seguridad: antes de ejecutar un UPDATE o DELETE masivo, envuélvelo en una transacción para poder hacer ROLLBACK si el resultado no es el esperado.
  • Rendimiento en inserciones masivas: envolver muchos INSERT en una sola transacción es mucho más rápido que ejecutarlos individualmente, porque SQLite solo escribe al disco una vez en lugar de una vez por cada INSERT.
-- Lento: cada INSERT es una transacción independiente
INSERT INTO logs VALUES (1, 'evento1');
INSERT INTO logs VALUES (2, 'evento2');
INSERT INTO logs VALUES (3, 'evento3');
-- ...miles más

-- Rápido: una sola transacción para todos
BEGIN TRANSACTION;
INSERT INTO logs VALUES (1, 'evento1');
INSERT INTO logs VALUES (2, 'evento2');
INSERT INTO logs VALUES (3, 'evento3');
-- ...miles más
COMMIT;

La diferencia de rendimiento puede ser de 100x o más en SQLite.

Nuestras tablas

Nombre de la Tabla: cuentas
idtitularsaldo
1Ana García1500
2Luis Pérez800
3Marta López2300
Nombre de la Tabla: movimientos
idcuenta_idtipocantidad
11ingreso1500
22ingreso800
33ingreso2300

Ejercicios