Transações e isolation levels: ACID sem decoreba
ACID é a garantia que diferencia um banco de dados de um arquivo de texto. Atomicidade, Consistência, Isolamento, Durabilidade — não são decoreba, são propriedades com implementação concreta que você precisa entender para não introduzir bugs de concorrência sutis.
ACID: o que cada propriedade garante
-- Atomicidade: tudo ou nada
BEGIN;
UPDATE contas SET saldo = saldo - 100 WHERE id = 1; -- debita Alice
UPDATE contas SET saldo = saldo + 100 WHERE id = 2; -- credita Bob
-- Se o segundo UPDATE falhar, o primeiro é REVERTIDO automaticamente
COMMIT; -- ambos ou nenhum
-- Consistência: constraints são verificadas no COMMIT
BEGIN;
UPDATE contas SET saldo = saldo - 1000 WHERE id = 1;
-- CHECK (saldo >= 0) é violado → ROLLBACK automático ao tentar COMMIT
COMMIT;
-- Isolamento: transações concorrentes não se veem (depende do nível)
-- Ver seção de Isolation Levels abaixo
-- Durabilidade: após COMMIT, dado está no disco (WAL garante)
-- PostgreSQL usa Write-Ahead Log: toda mudança vai para WAL antes do heap
-- Em caso de crash, recovery reproduz o WAL
-- synchronous_commit = on (default): COMMIT espera WAL estar em discoIsolation Levels: o trade-off consistência × performance
| Nível | Dirty Read | Non-Repeatable Read | Phantom Read | Anomalias seriais |
|---|---|---|---|---|
| Read Uncommitted | Possível* | Possível | Possível | Possível |
| Read Committed (padrão PG) | Impossível | Possível | Possível | Possível |
| Repeatable Read | Impossível | Impossível | Impossível* | Possível |
| Serializable | Impossível | Impossível | Impossível | Impossível |
-- Alterar isolation level para uma transação:
BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ;
-- ... operações ...
COMMIT;
-- Ou para a sessão inteira:
SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL SERIALIZABLE;
-- Exemplo de Non-Repeatable Read (Read Committed):
-- T1: BEGIN; SELECT saldo FROM contas WHERE id=1; → retorna 1000
-- T2: BEGIN; UPDATE contas SET saldo=500 WHERE id=1; COMMIT;
-- T1: SELECT saldo FROM contas WHERE id=1; → retorna 500 (mudou!)
-- Com Repeatable Read, T1 veria 1000 na segunda leitura
-- Serialization Anomaly (detectada pelo Serializable PG):
-- T1: SELECT SUM(quantidade) FROM estoque; → 100
-- T2: SELECT SUM(quantidade) FROM estoque; → 100
-- T1: INSERT INTO movimentos (tipo, qtd) VALUES ('entrada', 50) WHERE 100 < 200;
-- T2: INSERT INTO movimentos (tipo, qtd) VALUES ('saida', 100) WHERE 100 >= 50;
-- Ambas commitam — mas qualquer execução serial teria comportamento diferente!
-- Serializable aborta uma delas com: ERROR: could not serialize access| Dirty read | Non-repeatable read | Phantom read | |
|---|---|---|---|
| Read Committed | Impossível | Possível | Possível |
| Repeatable Read | Impossível | Impossível | Possível (no padrão SQL; Postgres evita na prática) |
| Serializable | Impossível | Impossível | Impossível — ao custo de mais conflito e retry |
O que a leitura suja significa, e qual nível de isolamento a permite?
Guia prático: use Read Committed (padrão) para operações CRUD simples. Use Repeatable Read para relatórios que precisam de snapshot consistente. Use Serializable quando a lógica tem read-modify-write complexo. Use SELECT FOR UPDATE SKIP LOCKED para filas de trabalho. Nunca faça read-modify-write sem transação em sistemas concorrentes.
Próximo: Normalização e modelagem — 1NF, 2NF, 3NF e quando desnormalizar intencionalmente.
Perguntas frequentes
❓ O que cada nível de isolamento evita?
❓ Preciso de serializável?
❓ Transação longa é problema?
Fixando
Duas transações leem o saldo, somam e gravam. Sem cuidado, uma sobrescreve a outra. Qual mecanismo resolve mantendo concorrência?
Numa fila de trabalho em tabela, vários processos disputam as mesmas linhas pendentes. Qual recurso evita que fiquem em fila esperando uns aos outros?
Terminou de ler?
Marcar como concluído registra o XP, mantém sua sequência e coloca 3 cartas deste módulo na fila de revisão espaçada.
Próximos passos sugeridos
Temas deste módulo
Discussão
Carregando comentários…