ProgramaciónIngeniero SQL

¿Cómo implementar el manejo de errores y transacciones en procedimientos almacenados en SQL para garantizar la integridad de la lógica de negocio? ¿Cuál es la diferencia entre los enfoques en diferentes SGBD?

Supere entrevistas con el asistente de IA Hintsage

Respuesta.

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:

  • Abrimos y cerramos explícitamente la transacción.
  • Manejamos errores en el bloque catch/exception y revertimos los cambios.
  • Registramos información sobre errores para auditoría.

Preguntas con trampa.

¿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.

Errores comunes y anti-patrones

  • Dejar COMMIT sin manejo de errores: registramos en la base de datos datos "deteriorados" o parcialmente cambiados.
  • Múltiples COMMIT/ROLLBACK dentro de una sola operación comercial.
  • Ignorar el registro de errores.

Ejemplo de la vida real

Caso negativo

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.

Caso positivo

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.