Historia de la pregunta
En las operaciones de procesamiento de la lógica de negocio, a menudo es necesario modificar datos en varias tablas relacionadas. Si algo falla (por ejemplo, debido a una restricción violada), es importante deshacer no solo la última operación, sino todas las acciones dentro de la lógica. Para esto se utiliza la mecánica de transacciones y el manejo de errores.
Problema
No todos los SGBD garantizan por defecto el rollback en procedimientos de múltiples pasos (especialmente si se utilizan TRY/CATCH, manejadores de EXCEPCIÓN). La escritura incorrecta de la lógica transaccional puede llevar a cambios "parciales" (parte de los datos se actualizó, parte no), lo que compromete la integridad del negocio.
Solución
Para un manejo correcto, se utilizan aperturas y cierres explícitos de transacciones (BEGIN TRANSACTION, COMMIT/ROLLBACK) junto con la captura de errores. La sintaxis y las capacidades varían en diferentes SGBD.
Ejemplo para MS SQL Server:
CREATE PROCEDURE update_balances(@from INT, @to INT, @amount DECIMAL(10,2)) AS BEGIN BEGIN TRY BEGIN TRANSACTION; UPDATE accounts SET balance = balance - @amount WHERE id = @from; UPDATE accounts SET balance = balance + @amount WHERE id = @to; COMMIT TRANSACTION; END TRY BEGIN CATCH ROLLBACK TRANSACTION; INSERT INTO error_log(err_message, err_date) VALUES(ERROR_MESSAGE(), GETDATE()); THROW; -- lanza el error hacia arriba END CATCH END;
Para PostgreSQL:
CREATE OR REPLACE FUNCTION update_balances(from_id INT, to_id INT, amount NUMERIC) RETURNS void AS $$ BEGIN UPDATE accounts SET balance = balance - amount WHERE id = from_id; UPDATE accounts SET balance = balance + amount WHERE id = to_id; -- La excepción revertirá automáticamente la transacción EXCEPTION WHEN OTHERS THEN INSERT INTO error_log(err_message, err_date) VALUES(SQLERRM, NOW()); RAISE; END; $$ LANGUAGE plpgsql;
Características clave:
¿Puede un error dentro de un procedimiento almacenado revertir automáticamente una transacción? ¡No! Por ejemplo, en MS SQL Server, ese rollback no está garantizado: se requiere un ROLLBACK explícito. En PostgreSQL, ante cualquier error, toda la transacción se vuelve "dañada" y requiere finalización.
¿Es posible "confirmar parcialmente" dentro de un solo procedimiento? Mala práctica. Se recomienda hacer commit/rollback solo una vez al final de la operación comercial. Algunas plataformas permiten "savepoints", pero esto es para tareas especiales.
¿Qué tipos de errores no se pueden capturar mediante TRY/CATCH/EXCEPTION en SQL? Algunos fallos del sistema (por ejemplo, la pérdida de conexión con el servidor) no llegarán al manejador y pueden hacer que el rollback sea imposible.
El procedimiento almacenado actualizaba tres tablas y finalizaba con COMMIT sin manejo de errores. Como resultado, al producirse un error en la segunda tabla, la primera ya había sido modificada, y se tuvo que "revertir" manualmente los datos de las copias de seguridad durante horas.
Ventajas:
Simple y "funciona", hasta que ocurre un fallo.
Desventajas:
Riesgo de inconsistencia; es imposible recuperarse rápidamente ante errores.
Implementamos un manejo explícito de errores + rollback de transacciones registrando todos los fallos en un log separado. Volver al estado íntegro de la base lleva segundos, y los fallos se analizan y minimizan.
Ventajas:
Garantía de inmutabilidad de los datos limpios incluso ante fallos; lógica transparente.
Desventajas:
Un poco más complicado para principiantes, requiere disciplina en el código.