El problema: operaciones a medias
Imagina una transferencia bancaria: restar 100 € de la cuenta A y sumarlos a la cuenta B. Son dos escrituras. ¿Qué pasa si el sistema falla entre las dos? El dinero desaparece: se restó de A pero nunca llegó a B.
Una transacción resuelve esto: agrupa varias operaciones en una unidad indivisible que se ejecuta entera o nada.
BEGIN, COMMIT, ROLLBACK
BEGIN;
UPDATE cuentas SET saldo = saldo - 100 WHERE id = 'A';
UPDATE cuentas SET saldo = saldo + 100 WHERE id = 'B';
COMMIT; -- confirma: ambos cambios se hacen permanentes a la vez
BEGIN(oBEGIN TRANSACTION) abre la transacción.COMMITla confirma: todos los cambios se aplican de golpe.ROLLBACKla deshace: descarta todos los cambios hechos desdeBEGIN, como si nada hubiera pasado.
BEGIN;
UPDATE cuentas SET saldo = saldo - 100 WHERE id = 'A';
-- algo va mal (saldo insuficiente, error de validación...)
ROLLBACK; -- nada se guarda; el saldo de A queda intacto
ACID: las cuatro garantías
Las transacciones de una base de datos relacional cumplen ACID:
- A — Atomicidad (Atomicity). Todo o nada. Si una operación de la transacción falla, todas se deshacen. Nunca queda a medias.
- C — Consistencia (Consistency). La transacción lleva la base de datos de
un estado válido a otro estado válido: se respetan las reglas
(claves foráneas, restricciones
CHECK,UNIQUE...). Nunca se viola una invariante de los datos. - I — Aislamiento (Isolation). Las transacciones concurrentes no se
pisan entre sí: cada una se ejecuta como si estuviera sola. Los resultados
intermedios de una no son visibles para las demás hasta el
COMMIT. - D — Durabilidad (Durability). Una vez hecho
COMMIT, los cambios persisten aunque se corte la luz justo después. Se han escrito a disco (normalmente mediante un write-ahead log).
Niveles de aislamiento
El aislamiento perfecto es caro (obliga a serializar todo). Por eso SQL define niveles que equilibran corrección y rendimiento, de menor a mayor garantía:
| Nivel | Permite | Riesgo que evita |
|---|---|---|
| Read Uncommitted | leer cambios no confirmados (dirty reads) | el menos seguro |
| Read Committed | solo leer datos ya confirmados | dirty reads |
| Repeatable Read | relecturas consistentes dentro de la transacción | non-repeatable reads |
| Serializable | como si las transacciones fueran en serie | phantom reads (el más seguro) |
Cuanto más alto el nivel, más correcto pero menos concurrente (más bloqueos).
Condiciones de carrera (race conditions)
Sin aislamiento adecuado aparecen condiciones de carrera: dos transacciones concurrentes leen el mismo dato, lo modifican y una sobrescribe a la otra (lost update).
Ejemplo clásico — reservar la última entrada de un concierto:
- Transacción T1 lee
entradas = 1. - Transacción T2 lee
entradas = 1(¡aún no se ha confirmado nada!). - T1 resta 1 → escribe
0. - T2 resta 1 → escribe
0.
Resultado: se han vendido dos entradas pero solo había una. Las
transacciones con el nivel de aislamiento adecuado (o un bloqueo explícito como
SELECT ... FOR UPDATE) evitan este error.
Regla mental: una transacción debe ser correcta aunque otra se ejecute al mismo tiempo.
Ejemplos
Transacción confirmada con COMMIT (transferencia)
BEGIN;
UPDATE pedidos SET estado = 'pagado' WHERE id = 2;
UPDATE pedidos SET estado = 'pagado' WHERE id = 6;
COMMIT;
SELECT id, estado FROM pedidos WHERE id IN (2, 6) ORDER BY id;
ROLLBACK deshace los cambios de la transacción
BEGIN;
UPDATE pedidos SET total = 0 WHERE id = 1;
ROLLBACK;
-- El total de id=1 sigue siendo el original (120.0)
SELECT id, total FROM pedidos WHERE id = 1;