Nessa aula, a gente vai focar nos erros típicos que rolam quando mexemos com transações e como fugir deles. Confia: até o SQL-hero mais brabo às vezes esquece de mandar um COMMIT! Vamos dar umas dicas pra esses vacilos virarem coisa rara.
Infelizmente (ou felizmente?), banco de dados não é castelo mágico onde tudo sempre funciona perfeito. Erros com transações são bem comuns, principalmente pra quem tá começando. Bora destrinchar isso aí.
Esquecendo o COMMIT ou ROLLBACK
Esquecer de finalizar a transação é o famoso "clássico do rolê". Imagina um restaurante onde você pediu a comida, mas o garçom esqueceu de trazer a conta. No mundo do PostgreSQL, isso significa que o banco "fica preso" no estado da transação, segurando recursos e travando outras operações.
Exemplo de erro:
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE account_id = 1;
-- Opa! Esquecemos de colocar COMMIT ou ROLLBACK.
Quando a transação "fica presa", o bloqueio pode pegar a tabela toda. Se o DBA sacar que deu ruim, ele pode finalizar ela "na marra". Mas melhor não deixar chegar nesse ponto.
Como evitar?
- Sempre finalize a transação de forma explícita:
COMMITouROLLBACK. - Use ferramentas de cliente que avisam automaticamente sobre transações penduradas.
- Se a transação não foi finalizada e você reiniciar o app, o banco vai dar
ROLLBACKautomático, mas nem sempre isso é legal pro estado do sistema.
Usando o nível de isolamento errado
Parecer besteira, mas escolher o nível de isolamento certo é chave pra evitar anomalias. Por exemplo, se você usa READ UNCOMMITTED numa operação financeira importante, pode acabar lendo dados "sujos" que depois vão ser desfeitos.
Exemplo:
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
BEGIN;
-- Lendo dados que outra transação tá mudando
SELECT balance FROM accounts WHERE account_id = 1;
-- Outra transação faz ROLLBACK e seus dados ficam inválidos.
Como evitar?
- Decida o quanto os dados são importantes pro seu app.
- Use
READ COMMITTEDna maioria dos casos pra evitar leitura "suja". - Use níveis mais rígidos tipo
REPEATABLE READouSERIALIZABLEquando for crítico evitar mudanças ou dados fantasmas.
Conflito de transações e locks
Às vezes duas ou mais transações tentam mudar os mesmos dados. Aí o PostgreSQL trava uma delas até a outra terminar. Isso pode virar um deadlock (bloqueio mútuo).
Exemplo de erro:
-- Primeira transação
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE account_id = 1;
-- Segunda transação
BEGIN;
UPDATE accounts SET balance = balance + 100 WHERE account_id = 1;
-- Esperando a primeira transação...
Se as duas seguram recursos que a outra precisa, rola deadlock. O PostgreSQL detecta, mata uma das transações e mostra a mensagem:
ERROR: deadlock detected
Como evitar?
- Siga sempre a mesma ordem de operações nas transações.
- Deixe as transações o mais rápidas possível pra diminuir o risco de lock.
- Use
SERIALIZABLEsó quando for realmente necessário.
Erro com SAVEPOINT
SAVEPOINT é massa pra fazer rollback parcial, mas se usar errado pode dar confusão. Por exemplo, se esquecer de liberar o savepoint (RELEASE SAVEPOINT), pode acabar com locks desnecessários ou erros.
Exemplo de erro:
BEGIN;
SAVEPOINT my_savepoint;
UPDATE accounts SET balance = balance - 100 WHERE account_id = 1;
ROLLBACK TO SAVEPOINT my_savepoint;
-- Esquecemos de liberar o SAVEPOINT!
Como evitar?
- Confere se você remove o
SAVEPOINTquando não precisa mais dele. - Evite criar savepoints demais pra não complicar as queries.
Incompatibilidade de transações com sistemas externos
Imagina que uma transação no PostgreSQL tenta interagir com sistemas externos: mandar notificação, atualizar API, etc. Se der ruim no sistema externo, fica difícil desfazer as mudanças.
Exemplo:
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE account_id = 1;
-- Não conseguiu mandar notificação: email-server não responde.
COMMIT; -- Mudanças salvas, mas notificação não foi enviada.
Como evitar?
- Se possível, separe as operações que falam com sistemas externos.
- Use tabelas intermediárias ou filas de tarefas pra coordenar ações com sistemas externos.
Erros por transações grandes demais
Transações grandes, com várias operações, tendem a ser mais vulneráveis a erros: locks, timeouts e deadlocks.
Exemplo:
BEGIN;
-- Milhares de operações de update
UPDATE orders SET status = 'completed' WHERE delivery_date < CURRENT_DATE;
COMMIT; -- Pode demorar bastante.
Como evitar?
- Quebre transações grandes em várias menores.
- Use batches pra atualizar os dados.
- Minimize a quantidade de dados alterados numa transação só.
Esquecendo de checar erros
Nem todo SQL dentro da transação vai rodar de boa. Se uma operação der erro, a transação toda fica zoada.
Exemplo:
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE account_id = -1; -- Erro: account_id não existe.
UPDATE accounts SET balance = balance + 100 WHERE account_id = 2;
COMMIT; -- Não rola por causa do erro.
Como evitar?
- Sempre confira o resultado de cada operação.
- Use tratamento de erro nas queries ou no código cliente.
Entendendo errado o comportamento do ROLLBACK
Muita gente acha que ROLLBACK desfaz tudo e volta ao estado inicial. Mas ROLLBACK só funciona dentro da transação atual.
Exemplo de confusão:
UPDATE accounts SET balance = balance - 100 WHERE account_id = 1;
ROLLBACK; -- Erro! Não funciona porque a operação não tava numa transação.
Como evitar?
- Lembre:
BEGINé seu parça, sem ele oROLLBACKnão faz nada. - Sempre coloque operações críticas dentro de transações.
GO TO FULL VERSION