Arquitetura de software · Dados · 8 min de leitura
Transações e níveis de isolamento
ACID promete que uma transação acontece por inteiro ou não acontece — mas o quanto ela enxerga das outras depende do nível de isolamento, e cada nível deixa passar anomalias diferentes.
Arraste para o lado para ver o diagrama inteiro.
ACID em uma frase cada
- Atomicidade: tudo ou nada — um erro no meio desfaz o que foi feito.
- Consistência: as regras (chaves,
CHECK, únicos) valem antes e depois. - Isolamento: transações simultâneas não se atrapalham — até certo ponto.
- Durabilidade: depois do
COMMIT, o dado sobrevive a uma queda (graças ao log de escrita antecipada).
As anomalias
- Leitura suja: ver dado de outra transação que ainda não confirmou.
- Leitura não repetível: ler a mesma linha duas vezes e obter valores diferentes.
- Fantasma: repetir uma consulta e aparecerem linhas novas.
- Atualização perdida: o caso do diagrama.
- Write skew: duas transações leem a mesma condição (“há outro médico de plantão”), cada uma altera uma linha diferente e juntas quebram a regra.
Os níveis na prática
- Read Committed — padrão do PostgreSQL: cada comando vê o que já foi confirmado. Sem leitura suja; o resto pode acontecer.
- Repeatable Read — padrão do MySQL/InnoDB; no PostgreSQL é snapshot isolation: a transação toda vê uma foto do início e detecta atualização perdida (erro de serialização), mas não write skew.
- Serializable: o resultado equivale a rodar uma transação de cada vez. No PostgreSQL usa SSI e pode abortar transações — a aplicação precisa repetir quando receber erro de serialização.
Os nomes são os mesmos em todos os bancos, mas o comportamento varia; o projeto Hermitage testa isso banco a banco.
Receitas
- Faça a conta no banco:
UPDATE contas SET saldo = saldo - 30 WHERE id = 1 AND saldo >= 30. - Trave o que vai alterar:
SELECT … FOR UPDATEdentro da transação. - Controle otimista: coluna
versaoeUPDATE … WHERE versao = 7; se nenhuma linha mudou, alguém chegou antes. - Transações curtas: nada de chamar API externa com a transação aberta.
No código: antes e depois
// Lê, calcula na aplicação e grava: duas requisições juntas perdem um saque.
const { rows } = await db.query('SELECT saldo FROM contas WHERE id = $1', [id]);
const novo = rows[0].saldo - valor;
if (novo < 0) throw new Error('saldo insuficiente');
await db.query('UPDATE contas SET saldo = $1 WHERE id = $2', [novo, id]); // Uma transação, e a conta feita pelo banco com a condição na mesma instrução.
await db.query('BEGIN');
try {
const r = await db.query(
'UPDATE contas SET saldo = saldo - $1 WHERE id = $2 AND saldo >= $1',
[valor, id],
);
if (r.rowCount === 0) throw new Error('saldo insuficiente');
await db.query('INSERT INTO lancamentos (conta_id, valor) VALUES ($1, $2)', [id, -valor]);
await db.query('COMMIT');
} catch (e) {
await db.query('ROLLBACK');
throw e;
} O UPDATE com saldo = saldo - $1 é atômico e a condição impede saldo negativo; o lançamento entra na mesma transação, então ou os dois acontecem ou nenhum.
Em resumo
- ACID: tudo ou nada, regras válidas, isolamento e durabilidade.
- Cada nível de isolamento permite anomalias diferentes.
- Read Committed é o padrão no PostgreSQL.
- Serializable exige repetir transações abortadas.
- Conta no banco, FOR UPDATE ou versão evitam atualização perdida.
Vantagens e custos
- Níveis mais fortes eliminam classes inteiras de bugs.
- Controle otimista não trava ninguém quando há pouca disputa.
- Serializable aborta mais e exige retry.
- FOR UPDATE cria filas e pode causar deadlock.
- Transações longas seguram travas e versões antigas.
Perguntar à IA
Monte um prompt pronto sobre Transações e níveis de isolamento na sua linguagem e no seu caso, e abra num chat gratuito.
O prompt vai para o site que você escolher. Não cole senhas, tokens nem dados reais de clientes. Para Gemini e outros, copie e cole.