Transacciones y bloqueos

Recorre la atomicidad con BEGIN/COMMIT/ROLLBACK, el rollback parcial con SAVEPOINT, AUTOCOMMIT, y los conceptos de bloqueos, niveles de aislamiento y las propiedades ACID, todo con diagramas y ejemplos de código de solo lectura.

Este artículo se construye sobre diagramas y código de solo lectura

Las transacciones solo cobran vida realmente sobre un RDBMS real donde múltiples usuarios acceden a la base de datos al mismo tiempo, y la consola en el navegador de este curso no puede reproducir fielmente ese comportamiento.

Por eso este artículo no tiene ejercicios ejecutables: lee los bloques de code de cada sección como ejemplos pensados para ejecutarse en un RDBMS real (MySQL / PostgreSQL / Oracle, etc.).

Qué es una transacción — tratar varias sentencias SQL como una sola unidad

Una transacción es una forma de tratar varias sentencias SQL como una sola unidad: si todas tienen éxito, se confirman juntas; si algo sale mal a mitad de camino, todas se revierten juntas.

Es esencial para cualquier proceso donde tener éxito solo en la mitad del trabajo sería un problema: piensa en transferencias bancarias (un UPDATE para debitar, uno para acreditar), o confirmar un pedido en línea (decrementar inventario, insertar la fila del pedido).

La forma básica es BEGIN; (iniciar) → varias sentencias → COMMIT; (confirmar) / ROLLBACK; (deshacer).

Los bloques de code en este artículo usan una tabla account que representa cuentas bancarias. Las tres columnas son account_id, owner (nombre del titular de la cuenta) y balance. Asume los siguientes datos iniciales mientras lees.

account_idownerbalance
1Alice1000
2Bob500
3Carol800
4Dave1200

Atomicidad — BEGIN ... COMMIT / ROLLBACK

Cuando haces BEGIN; y luego COMMIT;, cada cambio entre esas dos sentencias se aplica junto.

Ejecuta ROLLBACK; en lugar de eso y cada cambio después de BEGIN; se deshace, dejando la base de datos en su estado anterior al inicio de la transacción.

Esta propiedad de "todo o nada" se llama atomicidad: una unidad que no se puede dividir en piezas más pequeñas.

Cuando algo como una violación de restricción CHECK sale mal a mitad de la transacción, el movimiento estándar es ROLLBACK; para desarmar todo de forma segura.

La bifurcación COMMIT / ROLLBACK
BEGINintentar cambiosrevisar resultadoCOMMITconfirmar cambiosROLLBACKvolver al iniciotodo bienabortar / error
Después de BEGIN, COMMIT confirma los cambios; ROLLBACK devuelve todo al estado anterior a BEGIN. Incluso tras una violación de CHECK, ROLLBACK deshace todo de forma segura.
-- Transferencia: Alice -> Bob, 100 (pensado para correr en un RDBMS real)
BEGIN;
UPDATE account SET balance = balance - 100 WHERE owner = 'Alice';
UPDATE account SET balance = balance + 100 WHERE owner = 'Bob';
COMMIT;   -- ambos UPDATEs se confirman en el mismo instante

-- Deshacer: cambiamos de opinión / algo salió mal
BEGIN;
UPDATE account SET balance = balance - 300 WHERE owner = 'Alice';
UPDATE account SET balance = balance + 300 WHERE owner = 'Bob';
ROLLBACK; -- ambos UPDATEs se descartan juntos

SAVEPOINT — revertir solo parte de una transacción

SAVEPOINT name; coloca un marcador a mitad de camino dentro de una transacción, y ROLLBACK TO name; revierte solo hasta ese marcador.

Los cambios hechos antes del marcador se mantienen, así que puedes seguir con otras operaciones y hacer COMMIT; al final.

Un uso típico: un proceso en varios pasos como "confirmar pedido → otorgar puntos → decrementar inventario", donde solo falló el decremento de inventario y quieres reintentar solo ese paso. Es más granular que tirar toda la transacción con ROLLBACK;.

SAVEPOINT y ROLLBACK TO — rollback parcial
BEGINUPDATEAlice -100SAVEPOINT sp1marcador aquíUPDATEBob -100ROLLBACK TO sp1deshace solo lo de BobCOMMITconfirma solo Alice -100deshacercontinuar
Coloca un marcador con SAVEPOINT y deshaz solo los cambios posteriores a él con ROLLBACK TO. Los cambios anteriores al marcador se mantienen y se pueden confirmar con COMMIT al final.
-- Deshacer solo la segunda actualización con SAVEPOINT (pensado para correr en un RDBMS real)
BEGIN;
UPDATE account SET balance = balance - 100 WHERE owner = 'Carol';
SAVEPOINT sp1;
UPDATE account SET balance = balance - 100 WHERE owner = 'Dave';
ROLLBACK TO sp1;   -- deshace solo lo de Dave (Carol -100 se mantiene)
COMMIT;            -- confirma solo Carol -100

AUTOCOMMIT — por defecto, cada sentencia se confirma sola

Cuando ejecutas un UPDATE o INSERT independiente sin escribir BEGIN; antes, cada sentencia se confirma automáticamente por sí misma.

Eso se llama AUTOCOMMIT: el modo por defecto en el que cada sentencia se confirma en cuanto se ejecuta.

Para confirmar o deshacer varias sentencias como una sola unidad, tienes que iniciar explícitamente una transacción con BEGIN;.

AUTOCOMMIT vs. una transacción explícita
Sin BEGIN(AUTOCOMMIT)Con BEGINtransacción explícitaUPDATEse confirma al momentoUPDATEaún sin confirmarno se puede deshacerCOMMIT para confirmarROLLBACK para deshacer
Sin BEGIN, cada sentencia se confirma en el momento que corre. Con BEGIN, todo hasta COMMIT se vuelve una unidad que puedes revertir junta.

Bloqueos e interbloqueos — fundamentos de concurrencia

Las bases de datos del mundo real reciben acceso de muchos usuarios al mismo tiempo. Digamos que Alice tiene un saldo de 1000, y dos tiendas (X e Y) intentan cobrarle en el mismo instante: ambas podrían leer "1000" y restar de ese valor, y uno de esos débitos desaparece silenciosamente.

La defensa contra esto es un bloqueo (lock): mientras la transacción de un usuario toca una fila, cualquier otro que intente tocar la misma fila tiene que esperar. El primero en entrar bloquea la fila, y el segundo espera.

Pero hay un lado opuesto: si el usuario A bloquea cuentas en el orden "cuenta 1 → cuenta 2" y el usuario B las bloquea en el orden "cuenta 2 → cuenta 1", terminan esperando los bloqueos del otro para siempre. Eso es un interbloqueo (deadlock). El arreglo clásico es adquirir siempre los bloqueos en el mismo orden (por ejemplo, siempre por account_id ascendente). Si ocurre un interbloqueo, la base de datos abortará a la fuerza una de las transacciones para romper el ciclo.

Bloqueos y su callejón sin salida (interbloqueo)
Usuario Abloquea cuenta 1Usuario Bbloquea cuenta 2A pide cuenta 2y esperaB pide cuenta 1y esperaInterbloqueoambos esperan sin finLa BD abortaa la fuerza una de ellas
Mientras un usuario está actualizando una fila, esa fila está bloqueada y el otro usuario tiene que esperar. Si dos transacciones tienen cada una lo que la otra necesita, ambas esperan para siempre: un interbloqueo.

Niveles de aislamiento y ACID — repaso de terminología

Un nivel de aislamiento controla cuánto del trabajo en progreso de otras transacciones concurrentes puede ver una transacción dada.

Los ajustes más débiles (como READ COMMITTED) son más rápidos pero te dejan ver más de los cambios de otros, mientras que los más fuertes (como SERIALIZABLE) son más seguros pero fuerzan más espera.

Junto con la atomicidad (A), la consistencia (C), el aislamiento (I) y la durabilidad (D — una vez que ocurre un COMMIT, los datos quedan en disco y sobreviven a caídas o cortes de luz), estas cuatro conforman las propiedades ACID.

El comportamiento exacto de los bloqueos y los niveles de aislamiento varía según el producto, así que cuando construyas algo para producción, revisa las especificaciones de la base de datos que estás usando.

ACIDSignificado
A — AtomicidadLas sentencias entre BEGIN y COMMIT o se aplican todas o ninguna.
C — ConsistenciaLas restricciones CHECK, las claves foráneas y otras reglas de integridad se mantienen antes y después de la transacción.
I — AislamientoEl trabajo en progreso de otras transacciones concurrentes queda oculto según el nivel de aislamiento.
D — DurabilidadUn resultado confirmado queda en disco y sobrevive a cortes de luz o caídas.
QUIZ

Verificación de conocimientos

Responde cada pregunta una a una.

Pregunta 1¿Cuál es el nombre de la propiedad que trata los dos UPDATEs (débito + crédito) de una transferencia de dinero como una sola unidad, confirmándolos juntos si ambos tienen éxito o deshaciendo ambos si algo falla a mitad?

Pregunta 2¿Qué le ocurre a la tabla cuando ejecutas BEGIN; UPDATE ...; UPDATE ...; ROLLBACK;?

Pregunta 3¿Qué pasa cuando ejecutas un solo UPDATE por sí mismo, sin escribir BEGIN; antes?