PL/pgSQL Explicado
PL/pgSQL é a linguagem procedural embutida do PostgreSQL, e toda a sua proposta de valor é que ela roda dentro do mesmo processo do executor de consultas em vez de enviar linhas de um lado para o outro para um cliente.
Essa proximidade também é seu limite: PL/pgSQL não é uma linguagem de programação de propósito geral, é um código de "cola" para expressar fluxo de controle que o SQL puro não consegue, e recorrer a ela cedo demais troca a capacidade do planejador de otimizar sua lógica como uma única consulta.
Esta página constrói o modelo mental por trás de blocos, volatilidade, STRICT e a divisão função versus procedimento para que Noções Básicas de Funções, Essenciais de PL/pgSQL, Volatilidade e STRICT e Procedimentos Armazenados e CALL sejam lidos como um sistema coeso.
Resumo
- PL/pgSQL envolve um bloco de lógica procedural (
DECLARE/BEGIN/EXCEPTION/END) em torno de instruções SQL, rodando dentro do processo do banco de dados em vez de ir e voltar para um cliente. - Por que Importa: Manter o fluxo de controle próximo aos dados evita idas e vindas pela rede, e os marcadores corretos de volatilidade e STRICT permitem que o planejador cacheie, pule ou paralelize chamadas que, de outra forma, teria que executar cegamente.
- Conceitos Chave: estrutura de blocos, SPI (Server Programming Interface), volatilidade, STRICT, SECURITY DEFINER.
- Quando Usar: Lógica de múltiplos passos que realmente precisa de ramificação ou loops próximos aos dados, callbacks de gatilho (trigger) ou procedimentos em lote que precisam gerenciar suas próprias transações.
- Limitações / Trade-offs: PL/pgSQL troca a clareza baseada em conjuntos do SQL pelo controle procedural linha a linha, e esse controle é fácil de usar em excesso para lógica que uma consulta simples expressaria tão bem.
- Tópicos Relacionados: funções de trigger, planejamento e cache de consultas, controle de transações, limites de privilégio.
Fundamentos
Um corpo de função PL/pgSQL é um bloco: uma seção opcional DECLARE para variáveis locais, uma seção BEGIN/END contendo instruções e uma seção opcional EXCEPTION para tratamento de erros.
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 instrução SQL dentro desse bloco roda através da Server Programming Interface (SPI) do PostgreSQL, a mesma API interna que permite ao PL/pgSQL entregar uma string de consulta ao analisador, planejador e executor comuns e obter linhas de volta.
A variável especial FOUND é definida após cada instrução executada pela SPI para indicar se ela afetou ou retornou pelo menos uma linha, que é exatamente o que o exemplo acima usa para detectar uma conta ausente.
O fluxo de controle lê como qualquer linguagem procedural: IF/ELSIF/ELSE, LOOP/WHILE/FOR, e um cursor implícito é aberto automaticamente sempre que um loop FOR itera sobre os resultados de uma consulta.
O modelo mental mais simples é que PL/pgSQL é uma fina camada de script envolvendo instruções SQL comuns, não um substituto para a lógica baseada em conjuntos do próprio SQL.
Mecânicas e Interações
Uma função (CREATE FUNCTION) sempre roda dentro da transação que a chamou e retorna um valor com RETURN, enquanto um procedimento (CREATE PROCEDURE, invocado com CALL) pode emitir seu próprio COMMIT ou ROLLBACK e não retorna nada, a menos que use parâmetros OUT.
Essa divisão de controle de transação existe porque uma função pode ser chamada de dentro de uma expressão SQL maior, como uma lista SELECT ou uma cláusula WHERE, onde iniciar ou terminar uma transação no meio de uma instrução não faria sentido.
Volatilidade informa ao planejador o que ele pode assumir sobre chamadas repetidas: IMMUTABLE promete que a mesma entrada sempre produzirá a mesma saída sem efeitos colaterais, STABLE promete o mesmo resultado dentro do snapshot de uma única instrução, e VOLATILE (o padrão) não promete nada e força uma nova chamada a cada vez.
IMMUTABLE -> pode ser dobrado como constante (constant-folded), indexado em um índice de expressão
STABLE -> seguro para chamar uma vez por instrução, reutilizado entre linhas
VOLATILE -> reavaliado para cada linha, bloqueia muitas otimizaçõesMarcar uma função como STRICT diz ao PostgreSQL que, se qualquer argumento for NULL, a função deve retornar NULL imediatamente sem executar o corpo, o que economiza trabalho e remove uma classe inteira de bugs de tratamento de NULL da própria função.
Funções de trigger são um caso especial que retorna o pseudo-tipo trigger em vez de um tipo SQL normal, e o PostgreSQL as chama automaticamente com variáveis implícitas de linha NEW/OLD em vez de argumentos explícitos, que é o mecanismo compartilhado por trás de O Modelo de Trigger.
O tratamento de exceções funciona através da cláusula EXCEPTION WHEN, e como o PostgreSQL o implementa com um savepoint internamente, capturar um erro dentro de um loop grande tem um custo real por iteração que um job em lote bem projetado deve considerar.
Considerações Avançadas e Aplicações
SECURITY DEFINER muda com quais privilégios uma função roda: por padrão, uma função roda como SECURITY INVOKER (os privilégios do usuário chamador), enquanto SECURITY DEFINER a faz rodar como o proprietário da função, que é como você permite que um usuário de baixo privilégio execute uma ação privilegiada estritamente definida sem conceder a ele acesso amplo à tabela.
Esse poder vem com uma armadilha bem conhecida: uma função SECURITY DEFINER que não fixa seu próprio search_path pode ser enganada para resolver um nome não qualificado para um objeto malicioso que o chamador controla, razão pela qual toda função SECURITY DEFINER deve definir search_path explicitamente em sua definição.
A volatilidade também afeta a elegibilidade para consultas paralelas, já que uma função VOLATILE desabilita o paralelismo para a consulta que a chama, a menos que seja adicionalmente marcada como PARALLEL SAFE, então uma função que parece inofensiva pode silenciosamente forçar um plano serial em uma consulta que, de outra forma, seria paralelizável.
| Abordagem | Força | Fraqueza | Melhor Ajuste |
|---|---|---|---|
| Função SQL | Inserida na consulta chamadora, totalmente otimizável pelo planejador | Nenhuma lógica de controle procedural | Wrappers simples, colunas computadas, pequenas expressões reutilizáveis |
| Função PL/pgSQL | Controle procedural, tratamento de exceções, chamável de expressões SQL | Não pode gerenciar suas próprias transações | Lógica de múltiplos passos invocada de uma consulta ou outra função |
Procedimento (CALL) | Pode COMMIT/ROLLBACK durante a execução | Não pode ser chamado de dentro de uma expressão SQL | Jobs em lote longos que precisam de commits periódicos |
| Função de Trigger | Roda automaticamente em DML, vê NEW/OLD | Adiciona custo invisível a cada escrita na tabela | Aplicação de invariantes ou captura de alterações no momento da escrita |
Testar funções de banco de dados merece a mesma disciplina que testar código de aplicação, e como as funções PL/pgSQL rodam em uma transação por padrão, envolver um teste em uma transação que sempre faz rollback é uma maneira barata de exercitar os efeitos colaterais reais da função sem deixar dados de teste para trás.
Equívocos Comuns
- "PL/pgSQL é lento porque é interpretado." O overhead do interpretador é real, mas geralmente minúsculo comparado às instruções SQL que ele despacha através da SPI, que rodam através do mesmo planejador e executor de qualquer outra consulta.
- "Funções e procedimentos são intercambiáveis." Apenas um procedimento invocado com
CALLpode emitir seu próprioCOMMITouROLLBACK, uma função sempre roda dentro da transação do chamador. - "STRICT significa que o argumento é obrigatório."
STRICTsignifica que, se qualquer argumento forNULL, a função retornaNULLimediatamente sem executar seu corpo, não diz nada sobre se o chamador pode omitir o argumento. - "SECURITY DEFINER sempre roda como superusuário." Ele roda com os privilégios de quem é o proprietário da função, o que só é tão perigoso quanto as concessões reais desse proprietário, embora um
search_pathnão fixado ainda possa permitir que um chamador escale o que a função faz. - "VOLATILE é apenas o padrão seguro sem custo real." Deixar uma função
VOLATILEquando ela é de fato determinística desabilita o folding de constantes, indexação de expressões e elegibilidade de consulta paralela queIMMUTABLEouSTABLEteriam desbloqueado.
FAQs
O que realmente está dentro de um corpo de função PL/pgSQL?
Um único bloco: uma seção opcional DECLARE para variáveis locais, uma seção BEGIN/END de instruções e uma seção opcional EXCEPTION para tratamento de erros.
Como o PL/pgSQL realmente executa instruções SQL dentro de uma função?
Cada instrução SQL embutida é entregue à Server Programming Interface (SPI), que a roteia através do mesmo analisador, planejador e executor de qualquer consulta executada de um cliente.
O que a variável `FOUND` realmente me diz?
Ela reflete se a instrução executada mais recentemente pela SPI afetou ou retornou pelo menos uma linha, que é a maneira padrão de verificar se um UPDATE ou SELECT INTO realmente encontrou algo.
Uma função pode chamar `COMMIT`?
Não, apenas um procedimento invocado com CALL pode emitir COMMIT ou ROLLBACK, uma função sempre executa dentro da transação que a chamou.
Por que eu escolheria um procedimento em vez de uma função?
- Quando um job em lote precisa comitar o progresso periodicamente em vez de manter uma única transação gigante aberta
- Quando a lógica não tem um valor de retorno significativo para ser inserido em uma expressão SQL
- Quando você quer que o chamador a invoque com
CALLem vez de incorporá-la em uma consulta
O que marcar uma função como `STRICT` realmente muda?
Se qualquer argumento for NULL, o PostgreSQL retorna NULL imediatamente sem nunca executar o corpo da função, o que economiza trabalho e elimina uma classe de bugs de tratamento de NULL.
Qual é a diferença prática entre STABLE e VOLATILE?
Uma função STABLE tem a garantia de retornar o mesmo resultado para os mesmos argumentos dentro do snapshot de uma instrução, permitindo que o planejador a chame uma vez e reutilize o resultado, enquanto uma função VOLATILE deve ser chamada novamente para cada linha.
A volatilidade afeta algo além do cache?
Sim, funções IMMUTABLE podem suportar um índice de expressão, e funções VOLATILE desabilitam a consulta paralela para a instrução que as chama, a menos que também sejam marcadas como PARALLEL SAFE.
Qual é o risco com funções `SECURITY DEFINER` especificamente?
Se a função não fixar seu próprio search_path, um chamador pode manipular o search_path de sua sessão para enganar a função a resolver um nome não qualificado para um objeto que o chamador controla.
Como uma função de trigger difere de uma função regular?
Ela retorna o pseudo-tipo especial trigger, nunca é chamada diretamente com argumentos e, em vez disso, recebe variáveis implícitas de linha NEW/OLD quando o PostgreSQL a invoca automaticamente em um evento DML correspondente.
Por que o tratamento de exceções dentro de um loop parece caro?
Cada bloco EXCEPTION WHEN é implementado com um savepoint subjacente, então envolver o tratamento de erros em cada iteração de um loop grande adiciona um overhead real por iteração que vale a pena medir em jobs em lote.
Devo escrever lógica em PL/pgSQL ou SQL puro?
Prefira SQL puro sempre que a lógica for genuinamente baseada em conjuntos, e recorra ao PL/pgSQL apenas quando precisar de ramificação real, loops ou tratamento de exceções que uma única consulta não possa expressar.
Como devo testar uma função PL/pgSQL com segurança?
Envolva a chamada de teste em uma transação que sempre faz rollback no final, o que exercita os efeitos colaterais reais da função sem deixar dados de teste no banco de dados.
Relacionados
- Noções Básicas de Funções - Funções SQL versus PL/pgSQL, na prática
- Essenciais de PL/pgSQL - variáveis, fluxo de controle e
FOUNDna prática - Volatilidade e STRICT -
IMMUTABLE/STABLE/VOLATILEe efeitos no planejador - Riscos do SECURITY DEFINER - limites de privilégio e armadilhas de
search_path - Procedimentos Armazenados e CALL - controle de transação com
CALL - Noções Básicas de Triggers - funções de trigger como um caso especializado de PL/pgSQL
Versões da Pilha: Esta página foi escrita para PostgreSQL 18.4 (linha estável 18, linha de manutenção 17), onde a estrutura de blocos, volatilidade,
STRICTe o comportamento deSECURITY DEFINERdescritos aqui têm sido estáveis em lançamentos principais recentes.