En esta lección nos vamos a centrar en los errores más típicos que aparecen cuando curras con transacciones, y cómo esquivarlos. Créeme, hasta el crack más pro de SQL a veces se olvida de poner COMMIT! Te vamos a dar unos trucos para que los errores con transacciones sean cosa rara.
Por desgracia (o por suerte), las bases de datos no son un castillo mágico donde todo va perfecto siempre. Los errores con transacciones son bastante habituales, sobre todo si estás empezando. Vamos a verlos en detalle.
Olvidar el comando COMMIT o ROLLBACK
Olvidarse de cerrar una transacción es todo un "clásico". Imagínate un restaurante donde pides la comida pero el camarero se olvida de traerte la cuenta. En el mundo de PostgreSQL eso significa que la base de datos se queda "pillada" en estado de transacción, ocupando recursos y bloqueando otras operaciones.
Ejemplo de error:
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE account_id = 1;
-- Ups! Nos olvidamos de poner COMMIT o ROLLBACK.
Cuando una transacción se "queda colgada", el bloqueo puede afectar a toda la tabla. Si el admin de la base de datos ve que la cosa va mal, puede cerrarla "a lo bestia". Pero mejor no llegar a eso.
¿Cómo evitarlo?
- Cierra siempre la transacción de forma explícita:
COMMIToROLLBACK. - Usa herramientas de cliente que te avisen si tienes transacciones colgadas.
- Si la transacción no está cerrada y reinicias la app, la base de datos hará
ROLLBACKautomáticamente, pero no siempre es lo mejor para el estado del sistema.
Usar un nivel de aislamiento incorrecto
Elegir el nivel de aislamiento puede parecer un rollo, pero es clave para evitar anomalías. Por ejemplo, si usas READ UNCOMMITTED para una operación financiera importante, puedes leer datos "sucios" que luego se cancelan.
Ejemplo:
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
BEGIN;
-- Leemos datos que otra transacción está cambiando
SELECT balance FROM accounts WHERE account_id = 1;
-- Otra transacción hace ROLLBACK y tus datos ya no valen.
¿Cómo evitarlo?
- Piensa bien cuán importantes son los datos para tu app.
- Usa
READ COMMITTEDen la mayoría de casos para evitar lecturas "sucias". - Aplica niveles más estrictos como
REPEATABLE READoSERIALIZABLEcuando sea crítico evitar cambios o datos fantasma.
Conflicto de transacciones y bloqueos
A veces dos o más transacciones intentan cambiar los mismos datos. En ese caso, PostgreSQL bloquea una hasta que la otra termine. Esto puede acabar en un deadlock (bloqueo mutuo).
Ejemplo de error:
-- Primera transacción
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE account_id = 1;
-- Segunda transacción
BEGIN;
UPDATE accounts SET balance = balance + 100 WHERE account_id = 1;
-- Esperando a la primera transacción...
Si ambas transacciones tienen recursos que la otra necesita, hay un atasco. PostgreSQL detecta el deadlock, cancela una de las transacciones y muestra este mensaje:
ERROR: deadlock detected
¿Cómo evitarlo?
- Sigue siempre el mismo orden de operaciones en las transacciones.
- Haz que las transacciones duren lo menos posible para reducir bloqueos.
- Usa el nivel de aislamiento
SERIALIZABLEsolo si de verdad lo necesitas.
Error con SAVEPOINT
SAVEPOINT es una herramienta genial para hacer rollback parcial, pero si la usas mal puede ser un lío. Por ejemplo, si te olvidas de liberar el punto de guardado (RELEASE SAVEPOINT), puedes tener bloqueos o errores innecesarios.
Ejemplo de error:
BEGIN;
SAVEPOINT my_savepoint;
UPDATE accounts SET balance = balance - 100 WHERE account_id = 1;
ROLLBACK TO SAVEPOINT my_savepoint;
-- ¡Nos olvidamos de liberar el SAVEPOINT!
¿Cómo evitarlo?
- Asegúrate de borrar el
SAVEPOINTsi ya no lo necesitas. - No crees demasiados puntos de guardado para no complicar las consultas.
Incompatibilidad de transacciones con sistemas externos
Imagina que una transacción en PostgreSQL intenta interactuar con sistemas externos: enviar notificaciones, actualizar una API, etc. Si algo falla en el sistema externo, deshacer los cambios es complicado.
Ejemplo:
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE account_id = 1;
-- No se puede enviar la notificación: email-server no responde.
COMMIT; -- Los cambios se guardan, pero la notificación no se envió.
¿Cómo evitarlo?
- Si puedes, separa las operaciones que interactúan con sistemas externos.
- Usa tablas intermedias o colas de tareas para coordinar acciones con sistemas externos.
Errores por transacciones grandes
Las transacciones grandes, con muchas operaciones, suelen ser más vulnerables a errores: bloqueos, timeouts y deadlocks.
Ejemplo:
BEGIN;
-- Miles de operaciones de actualización
UPDATE orders SET status = 'completed' WHERE delivery_date < CURRENT_DATE;
COMMIT; -- Puede tardar bastante.
¿Cómo evitarlo?
- Divide las transacciones grandes en varias más pequeñas.
- Usa batches para actualizar datos.
- Minimiza la cantidad de datos que cambias en una sola transacción.
Olvidar comprobar errores
No todas las consultas SQL en una transacción van a salir bien. Por ejemplo, si una operación da error, toda la transacción falla.
Ejemplo:
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE account_id = -1; -- Error: account_id no existe.
UPDATE accounts SET balance = balance + 100 WHERE account_id = 2;
COMMIT; -- No se ejecuta por el error.
¿Cómo evitarlo?
- Comprueba siempre el resultado de cada operación.
- Usa manejo de errores en tus consultas o en el código cliente.
Malentender el comportamiento de ROLLBACK
Mucha gente piensa que ROLLBACK deshace los cambios y lo deja todo como antes. Pero ROLLBACK solo funciona dentro de la transacción actual.
Ejemplo de confusión:
UPDATE accounts SET balance = balance - 100 WHERE account_id = 1;
ROLLBACK; -- ¡Error! Esto no funciona porque la operación no estaba en una transacción.
¿Cómo evitarlo?
- Recuerda:
BEGINes tu colega, sin élROLLBACKno hace nada. - Envuelve siempre las operaciones críticas en transacciones.
GO TO FULL VERSION