En los sistemas modernos de bases de datos, la lógica de negocio muchas veces se implementa del lado del servidor — usando procedimientos y funciones. Si trabajas con PostgreSQL, es clave entender la diferencia entre funciones y procedimientos (sobre todo desde que existen los procedimientos desde la versión 11+) y cómo interactúan con las transacciones.
Aquí te cuento los puntos clave sobre la mecánica de transacciones, llamadas anidadas y rollback parcial de cambios en procedimientos/funciones de PostgreSQL 17, según la documentación oficial y las limitaciones actuales.
Conceptos clave: funciones vs procedimientos
Función (CREATE FUNCTION) — siempre se ejecuta dentro de una sola transacción externa; dentro de funciones no puedes usar comandos transaccionales explícitos (BEGIN, COMMIT, ROLLBACK, SAVEPOINT).
- Cualquier cambio se confirma o se revierte solo a nivel de la transacción externa.
- Para hacer un "rollback parcial" dentro de funciones se usa
BEGIN ... EXCEPTION ... END, pero esto no permite hacer commits dentro de la función.
Procedimiento (CREATE PROCEDURE) — apareció para poder manejar transacciones directamente en el servidor (por ejemplo, hacer commits parciales, rollback por etapas, etc).
- En procedimientos (PL/pgSQL) puedes usar
COMMIT,ROLLBACK,SAVEPOINT,RELEASE SAVEPOINT. - IMPORTANTE: no puedes usar
ROLLBACK TO SAVEPOINTen un procedimiento PL/pgSQL (te dará un error de sintaxis). - Los procedimientos solo se pueden llamar con el comando SQL
CALL ..., no conSELECTni dentro de otras funciones.
¿Cómo llamar un procedimiento/función desde otro?
Las funciones llaman a otras funciones de forma "transparente" usando el nombre normalmente:
-- Ejemplo: función para calcular descuento
CREATE OR REPLACE FUNCTION calcular_descuento(order_total NUMERIC)
RETURNS NUMERIC AS $$
BEGIN
IF order_total >= 100 THEN
RETURN order_total * 0.1;
ELSE
RETURN 0;
END IF;
END;
$$ LANGUAGE plpgsql;
-- Función de procesamiento de pedido llama a otra función
CREATE OR REPLACE FUNCTION procesar_pedido(order_id INT, order_total NUMERIC)
RETURNS VOID AS $$
DECLARE
descuento NUMERIC;
BEGIN
descuento := calcular_descuento(order_total);
RAISE NOTICE 'Descuento: %', descuento;
INSERT INTO orders_log (order_id, order_total, descuento)
VALUES (order_id, order_total, descuento);
END;
$$ LANGUAGE plpgsql;
¡Todo se ejecuta dentro de una sola transacción externa! Un error en cualquier función hará rollback de todos los cambios.
Llamada de procedimientos y transacciones anidadas
Los procedimientos se pueden llamar dentro de otros procedimientos usando el comando CALL ... (en PostgreSQL 17 se permite una pila de llamadas CALL proc1() -> CALL proc2()), pero las reglas de transacciones siguen igual:
- Los comandos de transacción (
COMMIT,ROLLBACK,SAVEPOINT,RELEASE SAVEPOINT) solo están disponibles en el nivel superior de los procedimientos. - Si un procedimiento con manejo de transacciones se llama dentro de una transacción explícita ya activa (por ejemplo, desde un cliente sin autocommit), intentar hacer
COMMIT/SAVEPOINTdará error.
no puedes ejecutar procedimientos dentro de funciones o bloques anónimos (DO ...). Solo con el comando CALL
Ejemplo de procedimiento con manejo de transacciones
-- Procedimiento con commit por etapas (solo funciona en modo autocommit de la conexión)
CREATE PROCEDURE procesar_pedidos_lote()
LANGUAGE plpgsql
AS $$
DECLARE
rec RECORD;
BEGIN
FOR rec IN SELECT order_id, order_total FROM incoming_orders LOOP
BEGIN
-- Guardamos cada lote de datos por separado
INSERT INTO orders (order_id, total) VALUES (rec.order_id, rec.order_total);
EXCEPTION WHEN OTHERS THEN
INSERT INTO order_errors(order_id, err_text) VALUES (rec.order_id, SQLERRM);
END;
COMMIT;
END LOOP;
END;
$$;
-- Llamada al procedimiento
CALL procesar_pedidos_lote();
Después de cada COMMIT empieza automáticamente una nueva transacción.
Rollback parcial (comportamiento tipo savepoint) en PL/pgSQL
PL/pgSQL (tanto en funciones como en procedimientos) no soporta el comando ROLLBACK TO SAVEPOINT.
Para revertir cambios de parte del código solo se usa el bloque BEGIN ... EXCEPTION ... END:
BEGIN
-- algunas acciones
BEGIN
-- operación que puede fallar
EXCEPTION WHEN OTHERS THEN
-- todos los cambios de este bloque se revierten
RAISE NOTICE '¡Rollback dentro del bloque!';
END;
END;
En procedimientos también puedes usar SAVEPOINT y RELEASE SAVEPOINT, pero no ROLLBACK TO SAVEPOINT. Su sentido es separar etapas, pero solo puedes manejarlas con manejo de excepciones.
Limitaciones y buenas prácticas
- Funciones — solo operaciones atómicas: todo o nada. Si algo falla — se revierte todo.
- Procedimientos — solo con CALL: y solo con comando SQL aparte, no desde SELECT/funciones. El manejo anidado de transacciones es posible, pero solo cumpliendo las limitaciones de PL/pgSQL.
- Rollback parcial — solo con EXCEPTION: es la forma recomendada y soportada oficialmente para rollback parcial (como un SAVEPOINT).
- Procedimientos anidados solo pueden manejar transacciones si se llaman con CALL: si no, habrá error.
Preguntas sobre la interacción de lógica y transacciones
¿Puedo hacer una "transacción anidada" dentro de una función?
No. Todo se ejecuta en una sola transacción. Para rollback parcial — solo bloques EXCEPTION.
¿Puedo hacer COMMIT/ROLLBACK dentro de una función o bloque anónimo?
No, eso es un error de sintaxis. Usa procedimientos.
¿Se puede llamar un procedimiento desde una función?
No, solo con el comando CALL. Desde función/SELECT — no se puede.
¿Se puede hacer ROLLBACK TO SAVEPOINT en un procedimiento?
¡No! En PL/pgSQL está prohibido. Usa bloques EXCEPTION.
GO TO FULL VERSION