Explicación de PL/pgSQL
PL/pgSQL es el lenguaje procedural incorporado de PostgreSQL, y toda su propuesta de valor es que se ejecuta dentro del mismo proceso que el ejecutor de consultas en lugar de enviar filas de un lado a otro a un cliente.
Esa proximidad también es su límite: PL/pgSQL no es un lenguaje de programación de propósito general, es un pegamento para expresar el flujo de control que el SQL plano no puede, y recurrir a él demasiado pronto renuncia a la capacidad del planificador para optimizar tu lógica como una sola consulta.
Esta página construye el modelo mental detrás de los bloques, la volatilidad, STRICT y la división función-versus-procedimiento para que Conceptos básicos de funciones, Fundamentos de PL/pgSQL, Volatilidad y STRICT y Procedimientos almacenados y CALL se lean como un sistema coherente.
Resumen
- PL/pgSQL envuelve un bloque de lógica procedural (
DECLARE/BEGIN/EXCEPTION/END) alrededor de sentencias SQL, ejecutándose dentro del proceso de la base de datos en lugar de hacer viajes de ida y vuelta a un cliente. - Por qué Importa: Mantener el flujo de control cerca de los datos evita viajes de ida y vuelta por la red, y los marcadores correctos de volatilidad y STRICT permiten al planificador almacenar en caché, omitir o paralelizar llamadas que de otro modo tendría que ejecutar a ciegas.
- Conceptos Clave: estructura de bloques, SPI (Interfaz de Programación del Servidor), volatilidad, STRICT, SECURITY DEFINER.
- Cuándo Usar: Lógica de varios pasos que realmente necesita ramificación o bucles cerca de los datos, callbacks de triggers, o procedimientos por lotes que necesitan gestionar sus propias transacciones.
- Limitaciones / Compensaciones: PL/pgSQL cambia la claridad basada en conjuntos de SQL por control procedural fila por fila, y ese control es fácil de usar en exceso para lógica que una consulta simple expresaría igual de bien.
- Temas Relacionados: funciones de trigger, planificación y caché de consultas, control de transacciones, límites de privilegios.
Fundamentos
El cuerpo de una función PL/pgSQL es un bloque: una sección opcional DECLARE para variables locales, una sección BEGIN/END que contiene sentencias, y una sección opcional EXCEPTION para el manejo de errores.
CREATE OR REPLACE FUNCTION app.bump_version(p_id bigint)
RETURNS void LANGUAGE plpgsql AS $$
BEGIN
UPDATE app.accounts SET name = name || ' v2' WHERE id = p_id;
IF NOT FOUND THEN
RAISE EXCEPTION 'missing account %', p_id;
END IF;
END;
$$;Cada sentencia SQL dentro de ese bloque se ejecuta a través de la Interfaz de Programación del Servidor (SPI) de PostgreSQL, la misma API interna que permite a PL/pgSQL entregar una cadena de consulta al analizador, planificador y ejecutor ordinarios y obtener filas de vuelta.
La variable especial FOUND se establece después de cada sentencia ejecutada por SPI para indicar si afectó o devolvió al menos una fila, que es exactamente lo que el ejemplo anterior utiliza para detectar una cuenta faltante.
El flujo de control se lee como cualquier lenguaje procedural: IF/ELSIF/ELSE, LOOP/WHILE/FOR, y un cursor implícito se abre automáticamente cada vez que un bucle FOR itera sobre los resultados de una consulta.
El modelo mental más simple es que PL/pgSQL es una capa de scripting delgada envuelta alrededor de sentencias SQL ordinarias, no un reemplazo para la lógica basada en conjuntos propia de SQL.
Mecánica e Interacciones
Una función (CREATE FUNCTION) siempre se ejecuta dentro de la transacción que la llamó y devuelve un valor con RETURN, mientras que un procedimiento (CREATE PROCEDURE, invocado con CALL) puede emitir su propio COMMIT o ROLLBACK y no devuelve nada a menos que utilice parámetros OUT.
Esa división en el control de transacciones existe porque una función puede ser llamada desde dentro de una expresión SQL más grande, como una lista SELECT o una cláusula WHERE, donde iniciar o finalizar una transacción a mitad de sentencia no tendría sentido.
La volatilidad le dice al planificador lo que puede asumir sobre las llamadas repetidas: IMMUTABLE promete que la misma entrada siempre produce la misma salida sin efectos secundarios, STABLE promete el mismo resultado dentro de la instantánea de una sola sentencia, y VOLATILE (el valor predeterminado) no promete nada y fuerza una nueva llamada cada vez.
IMMUTABLE -> puede ser plegado a constante, indexado en un índice de expresión
STABLE -> seguro para llamar una vez por sentencia, reutilizado entre filas
VOLATILE -> re-evaluado para cada fila, bloquea muchas optimizacionesMarcar una función como STRICT le dice a PostgreSQL que si algún argumento es NULL, la función debe devolver NULL inmediatamente sin ejecutar el cuerpo, lo que ahorra trabajo y elimina una clase de errores de manejo de NULL de la propia función.
Las funciones de trigger son un caso especial que devuelve el pseudo-tipo trigger en lugar de un tipo SQL normal, y PostgreSQL las llama automáticamente con variables de fila implícitas NEW/OLD en lugar de argumentos explícitos, que es la maquinaria compartida detrás de El Modelo de Triggers.
El manejo de excepciones funciona a través de la cláusula EXCEPTION WHEN, y dado que PostgreSQL lo implementa con un punto de salvaguarda internamente, capturar un error dentro de un bucle grande tiene un costo real por iteración que un trabajo por lotes bien diseñado debería tener en cuenta.
Consideraciones Avanzadas y Aplicaciones
SECURITY DEFINER cambia con qué privilegios se ejecuta una función: por defecto, una función se ejecuta como SECURITY INVOKER (los privilegios del usuario que llama), mientras que SECURITY DEFINER la hace ejecutarse como el propietario de la función, que es cómo se permite a un usuario de bajo privilegio realizar una acción privilegiada de alcance limitado sin otorgarle acceso amplio a tablas.
Ese poder viene con una trampa bien conocida: una función SECURITY DEFINER que no fija su propio search_path puede ser engañada para resolver un nombre no calificado a un objeto malicioso que el llamador controla, razón por la cual cada función SECURITY DEFINER debe establecer explícitamente search_path en su definición.
La volatilidad también afecta la elegibilidad para consultas paralelas, ya que una función VOLATILE deshabilita el paralelismo para la consulta que la llama a menos que también esté marcada como PARALLEL SAFE, por lo que una función que parece inofensiva puede forzar silenciosamente un plan serial en una consulta que de otro modo sería paralelizable.
| Enfoque | Fortaleza | Debilidad | Mejor Ajuste |
|---|---|---|---|
| Función SQL | Se inserta en la consulta que la llama, totalmente optimizable por el planificador | Ningún flujo de control procedural en absoluto | Envolturas simples, columnas calculadas, expresiones reutilizables pequeñas |
| Función PL/pgSQL | Control procedural, manejo de excepciones, invocable desde expresiones SQL | No puede gestionar sus propias transacciones | Lógica de varios pasos invocada desde una consulta u otra función |
Procedimiento (CALL) | Puede hacer COMMIT/ROLLBACK a mitad de ejecución | No se puede llamar desde dentro de una expresión SQL | Trabajos por lotes largos que necesitan commits periódicos |
| Función de trigger | Se ejecuta automáticamente en DML, ve NEW/OLD | Añade costo invisible a cada escritura en la tabla | Aplicación de invariantes o captura de cambios en el momento de la escritura |
Probar funciones de base de datos merece la misma disciplina que probar código de aplicación, y dado que las funciones PL/pgSQL se ejecutan dentro de una transacción por defecto, envolver una prueba en una transacción que siempre se revierte es una forma barata de ejercitar los efectos secundarios reales de una función sin dejar datos de prueba en la base de datos.
Conceptos Erróneos Comunes
- "PL/pgSQL es lento porque es interpretado." La sobrecarga del intérprete es real pero generalmente minúscula en comparación con las sentencias SQL que despacha a través de SPI, las cuales se ejecutan a través del mismo planificador y ejecutor que cualquier otra consulta.
- "Funciones y procedimientos son intercambiables." Solo un procedimiento invocado con
CALLpuede emitir su propioCOMMIToROLLBACK, una función siempre se ejecuta dentro de la transacción del llamador. - "STRICT significa que el argumento es obligatorio."
STRICTsignifica que si algún argumento esNULL, la función devuelveNULLinmediatamente sin ejecutar su cuerpo, no dice nada sobre si el llamador puede omitir el argumento. - "SECURITY DEFINER siempre se ejecuta como superusuario." Se ejecuta con los privilegios de quien posee la función, lo cual es solo tan peligroso como los permisos reales de ese propietario, aunque un
search_pathno fijado aún puede permitir a un llamador escalar lo que hace la función. - "VOLATILE es solo el valor predeterminado seguro sin costo real." Dejar una función
VOLATILEcuando en realidad es determinista deshabilita el plegado de constantes, los índices de expresiones y la elegibilidad de consultas paralelas queIMMUTABLEoSTABLEhabrían desbloqueado.
Preguntas Frecuentes
¿Qué hay realmente dentro del cuerpo de una función PL/pgSQL?
Un solo bloque: una sección opcional DECLARE para variables locales, una sección BEGIN/END de sentencias, y una sección opcional EXCEPTION para el manejo de errores.
¿Cómo ejecuta realmente PL/pgSQL sentencias SQL dentro de una función?
Cada sentencia SQL incrustada se entrega a la Interfaz de Programación del Servidor (SPI), que la dirige a través del mismo analizador, planificador y ejecutor que cualquier consulta ejecutada desde un cliente.
¿Qué me dice realmente la variable `FOUND`?
Refleja si la sentencia ejecutada más recientemente por SPI afectó o devolvió al menos una fila, que es la forma estándar de verificar si una UPDATE o SELECT INTO realmente coincidió con algo.
¿Puede una función llamar a `COMMIT`?
No, solo un procedimiento invocado con CALL puede emitir COMMIT o ROLLBACK; una función siempre se ejecuta dentro de la transacción que la llamó.
¿Por qué elegiría un procedimiento en lugar de una función?
- Cuando un trabajo por lotes necesita confirmar el progreso periódicamente en lugar de mantener una única transacción gigante abierta.
- Cuando la lógica no tiene un valor de retorno significativo para insertar en una expresión SQL.
- Cuando desea que el llamador lo invoque con
CALLen lugar de incrustarlo en una consulta.
¿Qué cambia realmente al marcar una función como `STRICT`?
Si algún argumento es NULL, PostgreSQL devuelve NULL inmediatamente sin ejecutar nunca el cuerpo de la función, lo que ahorra trabajo y elimina una clase de errores de manejo de NULL.
¿Cuál es la diferencia práctica entre STABLE y VOLATILE?
Una función STABLE tiene garantizado que devolverá el mismo resultado para los mismos argumentos dentro de la instantánea de una sentencia, permitiendo al planificador llamarla una vez y reutilizar el resultado, mientras que una función VOLATILE debe ser llamada de nuevo para cada fila.
¿La volatilidad afecta algo más que el almacenamiento en caché?
Sí, las funciones IMMUTABLE pueden respaldar un índice de expresión, y las funciones VOLATILE deshabilitan las consultas paralelas para la sentencia que las llama a menos que también estén marcadas como PARALLEL SAFE.
¿Cuál es el riesgo específico con las funciones `SECURITY DEFINER`?
Si la función no fija su propio search_path, un llamador puede manipular el search_path de su sesión para engañar a la función y hacer que resuelva un nombre no calificado a un objeto que el llamador controla.
¿En qué se diferencia una función de trigger de una función regular?
Devuelve el pseudo-tipo especial trigger, nunca se llama directamente con argumentos y, en cambio, recibe variables de fila implícitas NEW/OLD cuando PostgreSQL la invoca automáticamente en un evento DML coincidente.
¿Por qué el manejo de excepciones dentro de un bucle se siente costoso?
Cada bloque EXCEPTION WHEN se implementa con un punto de salvaguarda subyacente, por lo que envolver el manejo de errores alrededor de cada iteración de un bucle grande añade un costo real por iteración que vale la pena medir en trabajos por lotes.
¿Debería escribir lógica en PL/pgSQL o en SQL plano?
Prefiera SQL plano siempre que la lógica sea genuinamente basada en conjuntos, y recurra a PL/pgSQL solo cuando necesite ramificación real, bucles o manejo de excepciones que una sola consulta no pueda expresar.
¿Cómo debería probar una función PL/pgSQL de forma segura?
Envuelva la llamada de prueba en una transacción que siempre se revierte al final, lo que ejercita los efectos secundarios reales de la función sin dejar datos de prueba en la base de datos.
Relacionados
- Conceptos básicos de funciones - Funciones SQL versus PL/pgSQL, práctico
- Fundamentos de PL/pgSQL - variables, flujo de control y
FOUNDen la práctica - Volatilidad y STRICT -
IMMUTABLE/STABLE/VOLATILEy efectos en el planificador - Riesgos de SECURITY DEFINER - límites de privilegios y trampas de
search_path - Procedimientos almacenados y CALL - control de transacciones con
CALL - Conceptos básicos de Triggers - funciones de trigger como un caso especializado de PL/pgSQL
Versiones de Stack: Esta página fue escrita para PostgreSQL 18.4 (línea estable 18, línea de mantenimiento 17), donde la estructura de bloques, la volatilidad,
STRICTy el comportamiento deSECURITY DEFINERdescritos aquí han sido estables en las versiones principales recientes.