Programar em Postgres é tipo uma aventura: às vezes parece um jogo chamado “Ache seu erro”. Aqui a gente vai falar dos erros clássicos e das armadilhas que aparecem quando você mexe com transações aninhadas. Bora lá!
Uso errado de comandos transacionais dentro de funções e procedimentos
Erro: tentar usar COMMIT, ROLLBACK ou SAVEPOINT dentro de uma FUNCTION.
Por quê: No PostgreSQL, funções (CREATE FUNCTION ... LANGUAGE plpgsql) sempre rodam dentro de uma transação externa, e qualquer comando transacional dentro da função é proibido. Se tentar, vai dar erro de sintaxe.
Exemplo de erro:
CREATE OR REPLACE FUNCTION f_bad() RETURNS void AS $$
BEGIN
SAVEPOINT sp1; -- Erro: comandos transacionais são proibidos
END;
$$ LANGUAGE plpgsql;
Como fazer certo:
Para operações atômicas, que precisam ser “tudo ou nada”, use funções sem comandos transacionais explícitos. Se precisar salvar mudanças em etapas — use procedimentos.
Erro: tentar usar ROLLBACK TO SAVEPOINT em um procedimento em PL/pgSQL.
Por quê: No PostgreSQL 17 só são permitidos os comandos COMMIT, ROLLBACK, SAVEPOINT, RELEASE SAVEPOINT dentro de procedimentos (CREATE PROCEDURE ... LANGUAGE plpgsql). Mas ROLLBACK TO SAVEPOINT em PL/pgSQL não pode! Qualquer tentativa vai dar erro de sintaxe.
Exemplo de erro:
CREATE PROCEDURE p_bad()
LANGUAGE plpgsql
AS $$
BEGIN
SAVEPOINT sp1;
-- ...
ROLLBACK TO SAVEPOINT sp1; -- Erro! Não pode usar
END;
$$;
Como fazer certo:
Para “rollback parcial” usa blocos BEGIN ... EXCEPTION ... END — eles criam um savepoint automático; se rolar erro dentro do bloco, tudo volta pro começo dele.
CREATE PROCEDURE p_good()
LANGUAGE plpgsql
AS $$
BEGIN
BEGIN
-- operações que podem dar erro
...
EXCEPTION
WHEN OTHERS THEN
RAISE NOTICE 'Rollback dentro do bloco BEGIN ... EXCEPTION ... END';
END;
END;
$$;
Chamadas aninhadas de procedimentos: limitações e erros comuns
Erro: chamar um procedimento com COMMIT/ROLLBACK explícito dentro de uma transação já aberta pelo cliente.
Por quê: procedimentos com controle transacional só funcionam direito no modo autocommit (um procedimento — uma transação), senão, se tentar usar COMMIT ou ROLLBACK dentro do procedimento, vai dar erro: a transação já tá aberta no cliente.
Exemplo:
# No Python com psycopg2, por padrão autocommit=False
cur.execute("BEGIN;")
cur.execute("CALL my_proc();") -- Erro ao tentar COMMIT dentro do my_proc
Como fazer certo:
- Antes de chamar procedimentos, coloca a conexão em modo autocommit.
- Não chama procedimentos por funções ou SELECT.
Erro: chamar procedimentos com controle transacional (COMMIT, ROLLBACK) não funciona se não for pelo comando CALL (tipo, via SELECT).
Por quê: Só chamada via CALL (ou num bloco DO anônimo) permite controlar transações. Chamar de função — não pode.
Problemas com locks e deadlocks
Locks são tipo visita indesejada: primeiro incomodam, depois viram bagunça. Deadlock rola quando as transações ficam esperando uma pela outra pra sempre. Olha um exemplo clássico:
- Transação A trava uma linha na tabela
orderse tenta atualizar uma linha na tabelaproducts. - Transação B trava uma linha na tabela
productse tenta atualizar uma linha na tabelaorders.
No fim, nenhuma das transações consegue continuar. É tipo dois carros tentando entrar na mesma curva ao mesmo tempo — vira engarrafamento.
Exemplo:
-- Transação A
BEGIN;
UPDATE orders SET status = 'Processando' WHERE id = 1;
-- Transação B
BEGIN;
UPDATE products SET stock = stock - 1 WHERE id = 10;
-- Agora a transação A tenta atualizar a mesma linha em `products`,
-- e a transação B tenta mudar a linha em `orders`.
-- Deadlock!
Como evitar?
- Sempre atualize os dados na mesma ordem. Tipo, primeiro
orders, depoisproducts. - Evite transações demoradas demais.
- Use
LOCKcom cuidado, escolhendo o menor nível de lock possível.
Uso errado de SQL dinâmico (EXECUTE)
SQL dinâmico, se usar sem cuidado, pode virar dor de cabeça. O erro mais comum é SQL injection. Tipo assim:
EXECUTE 'SELECT * FROM orders WHERE id = ' || user_input;
Se user_input for algo tipo 1; DROP TABLE orders;, já era a tabela orders.
Como evitar? Use queries preparadas:
EXECUTE 'SELECT * FROM orders WHERE id = $1' USING user_input;
Assim seu app fica protegido de SQL injection.
Rollback da transação depois de tratar erro errado
Se os erros não forem tratados direito, a transação pode ficar num estado inválido. Tipo assim:
BEGIN;
INSERT INTO orders (order_id, status) VALUES (1, 'Pendente');
BEGIN;
-- Alguma operação que dá erro
INSERT INTO non_existing_table VALUES (1);
-- Erro, mas a transação não foi finalizada
COMMIT; -- Erro: transação atual foi abortada
Por causa do erro, o código trava tudo.
Como evitar? Use blocos EXCEPTION pra fazer rollback direito:
BEGIN
INSERT INTO orders (order_id, status) VALUES (1, 'Pendente');
EXCEPTION
WHEN OTHERS THEN
ROLLBACK;
RAISE NOTICE 'Rolou um erro, a transação vai ser desfeita.';
END;
Como evitar erros: dicas e recomendações
- Quando for escrever um procedimento complicado, começa com um pseudocódigo. Escreve todos os passos e onde pode dar ruim.
- Use
SAVEPOINTpra rollback isolado. Mas não esquece de liberar depois de usar. - Evite transações longas — quanto mais tempo, mais chance de lock.
- Pra chamadas aninhadas de procedimentos, garante que o contexto de transação externo e interno estão sincronizados.
- Sempre testa a performance dos seus procedimentos com
EXPLAIN ANALYZE. - Loga os erros em tabelas ou arquivos de texto — isso ajuda muito na hora de debugar.
Exemplos de erros e como corrigir
Exemplo 1: Erro ao chamar procedimento aninhado
Código com erro:
BEGIN;
CALL process_order(5);
-- Dentro do process_order rolou um ROLLBACK
-- A transação inteira fica inválida
COMMIT; -- Erro
Código corrigido:
BEGIN;
SAVEPOINT sp_outer;
CALL process_order(5);
-- Rollback só se der erro
ROLLBACK TO SAVEPOINT sp_outer;
COMMIT;
Exemplo 2: Problema de Deadlock
Código com erro:
-- Transação A
BEGIN;
UPDATE orders SET status = 'Processando' WHERE id = 1;
-- Espera `products`
-- Transação B
BEGIN;
UPDATE products SET stock = stock - 1 WHERE id = 10;
-- Espera `orders`
Correção:
-- As duas queries rodam na mesma ordem:
-- Primeiro `products`, depois `orders`.
BEGIN;
UPDATE products SET stock = stock - 1 WHERE id = 10;
UPDATE orders SET status = 'Processando' WHERE id = 1;
COMMIT;
Esses erros mostram porque trabalhar com transações exige atenção e experiência. Mas, como dizem, quanto mais prática, menos chance de tomar um ROLLBACK na vida real (e na carreira).
GO TO FULL VERSION