Uncommitted vs Committed Reads: quando uma leitura pode mentir para você
Quando falamos sobre banco de dados, é comum pensar primeiro em tabelas, queries, índices e performance.
Mas existe um ponto muito importante que às vezes passa despercebido:
O dado que estou lendo já foi realmente confirmado?
Essa pergunta é essencial quando começamos a falar sobre transações.
Em sistemas reais, várias operações podem acontecer ao mesmo tempo. Um usuário está fazendo um pagamento, outro está atualizando um pedido, outro está baixando estoque, outro está consultando saldo.
E no meio disso tudo, o banco precisa responder uma pergunta difícil:
Uma transação pode ler dados que outra transação ainda não confirmou?
É aqui que entram os conceitos de Uncommitted Read e Committed Read.
Uncommitted Read
Uncommitted Read acontece quando uma transação lê dados que ainda não foram confirmados por outra transação.
Esse problema também é conhecido como:
Dirty Read
Ou seja, uma “leitura suja”.
Vamos para um exemplo simples.
Imagine que Luis tem R$1000 de saldo.
Saldo inicial: R$1000
Agora começa uma transação:
Transação A:
- debita R$100 da conta
- saldo temporário fica R$900
- ainda não deu COMMIT
Enquanto isso, outra transação faz uma leitura:
Transação B:
- lê o saldo como R$900
Até aqui parece tudo bem.
Mas agora acontece um problema:
Transação A:
- falha
- executa ROLLBACK
O saldo volta para R$1000.
Saldo real: R$1000
Só que a Transação B já tinha lido R$900.
Ela leu um dado que nunca existiu oficialmente.
Esse é o problema do Uncommitted Read.
Exemplo visual
Saldo inicial: R$1000
Transação A:
UPDATE saldo para R$900
sem COMMIT
Transação B:
lê saldo = R$900
Transação A:
ROLLBACK
Saldo real volta para R$1000
A Transação B tomou uma decisão baseada em um dado temporário.
Esse dado poderia nunca ser confirmado.
E foi exatamente o que aconteceu.
Por que isso é perigoso?
Porque o sistema passa a trabalhar com uma realidade falsa.
Em sistemas simples, isso talvez gere apenas uma informação errada na tela.
Mas em sistemas críticos, pode causar problemas sérios.
Exemplos:
- saldo bancário;
- processamento de pagamento;
- reserva de estoque;
- emissão de pedido;
- reserva de hotel;
- compra de ingresso;
- sistemas médicos;
- sistemas jurídicos;
- relatórios financeiros.
Imagine um sistema financeiro lendo um saldo reduzido antes do COMMIT.
Ou um sistema de estoque lendo uma baixa que depois foi desfeita.
Ou um sistema de reserva considerando uma vaga ocupada, mas a transação original falhou.
O problema não é apenas “mostrar um dado errado”.
O problema é tomar decisões com base em um dado que nunca foi válido.
Committed Read
Committed Read significa que uma transação só consegue ler dados que já foram confirmados com COMMIT.
Ou seja:
Se outra transação alterou um dado, mas ainda não confirmou, eu não leio esse valor temporário.
Voltando ao exemplo do saldo:
Saldo inicial: R$1000
A Transação A começa:
Transação A:
- altera saldo para R$900
- ainda não deu COMMIT
Agora a Transação B tenta ler o saldo:
Transação B:
- continua vendo R$1000
Por quê?
Porque R$900 ainda não foi confirmado.
Depois a Transação A confirma:
Transação A:
COMMIT
Agora sim:
Transação B:
- pode ler R$900
Essa abordagem evita dirty reads.
A Transação B só lê dados oficialmente confirmados.
Comparando os dois
Uncommitted Read
Transação A altera o dado
Transação A ainda não confirma
Transação B lê o dado alterado
Transação A faz ROLLBACK
Transação B leu um dado inválido
Problema:
A leitura pode enxergar dados temporários que talvez nunca sejam confirmados.
Committed Read
Transação A altera o dado
Transação A ainda não confirma
Transação B não vê essa alteração
Transação A faz COMMIT
Transação B agora pode ver o novo valor
Benefício:
A leitura só enxerga dados confirmados.
Read Uncommitted vs Read Committed
Em bancos relacionais, esses conceitos aparecem como níveis de isolamento.
Read Uncommitted
É o nível onde uma transação pode ler dados não commitados por outra transação.
Ele permite dirty reads.
Em teoria, pode oferecer mais concorrência, porque as transações bloqueiam menos umas às outras.
Mas o custo é alto:
- maior risco de inconsistência;
- leitura de dados temporários;
- decisões baseadas em informações inválidas;
- comportamento difícil de prever.
Na prática, é um nível perigoso para a maioria dos sistemas de negócio.
Read Committed
É um nível de isolamento mais seguro.
Ele garante que uma transação só leia dados que já passaram por COMMIT.
Isso evita dirty reads.
Por isso, costuma ser uma escolha comum em muitos bancos relacionais.
Mas é importante entender uma coisa:
Read Committed evita dirty reads, mas não resolve todos os problemas de concorrência.
Ainda podem acontecer outros fenômenos, como:
- non-repeatable reads;
- phantom reads.
Non-repeatable Read
Mesmo usando Read Committed, uma transação pode ler o mesmo registro duas vezes e receber valores diferentes.
Exemplo:
Transação A:
SELECT balance FROM accounts WHERE id = 1;
Resultado: R$1000
Transação B:
UPDATE accounts SET balance = 900 WHERE id = 1;
COMMIT;
Transação A:
SELECT balance FROM accounts WHERE id = 1;
Resultado: R$900
A Transação A leu apenas dados commitados.
Então não houve dirty read.
Mas o valor mudou dentro da mesma transação.
Esse fenômeno é chamado de non-repeatable read.
Phantom Read
Também pode acontecer de uma query retornar uma quantidade diferente de linhas dentro da mesma transação.
Exemplo:
Transação A:
SELECT * FROM orders WHERE status = 'pending';
Resultado: 10 pedidos
Transação B:
INSERT INTO orders(status) VALUES ('pending');
COMMIT;
Transação A:
SELECT * FROM orders WHERE status = 'pending';
Resultado: 11 pedidos
A nova linha apareceu no meio da transação.
Isso é chamado de phantom read.
De novo: não foi dirty read, porque a nova linha foi commitada.
Mas ainda assim o resultado mudou.
Tabela simplificada
| Nível de isolamento | Evita Dirty Read? | Evita Non-repeatable Read? | Evita Phantom Read? |
|---|---|---|---|
| Read Uncommitted | Não | Não | Não |
| Read Committed | Sim | Não | Não |
| Repeatable Read | Sim | Sim | Depende do banco |
| Serializable | Sim | Sim | Sim |
Quanto maior o isolamento, maior a consistência.
Mas também aumenta o custo.
O tradeoff
Em banco de dados, quase tudo é tradeoff.
Quanto maior o isolamento:
- maior a segurança dos dados;
- maior a consistência;
- menor o risco de leituras incorretas;
- maior a chance de locks;
- menor a concorrência;
- menor o throughput.
Quanto menor o isolamento:
- maior a concorrência;
- menor a espera entre transações;
- maior a performance em alguns cenários;
- maior o risco de inconsistência.
Então a pergunta não é simplesmente:
Qual nível é melhor?
A pergunta correta é:
Qual nível de inconsistência minha regra de negócio consegue tolerar?
Exemplo prático: saldo bancário
Para saldo bancário, Uncommitted Read seria extremamente perigoso.
Imagine:
Saldo inicial: R$1000
Transação A:
debita R$900
saldo temporário: R$100
sem COMMIT
Transação B:
lê saldo como R$100
Transação A:
ROLLBACK
Saldo real volta para R$1000
A Transação B viu um saldo falso.
Se o sistema tomar uma decisão com esse dado, pode bloquear uma compra, negar uma operação ou gerar uma informação incorreta para o usuário.
Nesse tipo de cenário, você quer ler apenas dados confirmados.
Exemplo prático: estoque
Estoque também exige cuidado.
Imagine:
Estoque inicial: 10 unidades
Transação A:
remove 5 unidades
estoque temporário: 5
sem COMMIT
Transação B:
lê estoque como 5
Transação A:
ROLLBACK
Estoque real volta para 10
A Transação B leu um estoque menor do que o real.
Dependendo do fluxo, isso pode impedir uma venda sem necessidade.
O contrário também pode acontecer em outros cenários: o sistema pode acreditar que existe estoque disponível quando, na prática, uma transação já confirmou a baixa.
Por isso, estoque geralmente precisa de controle transacional bem definido.
Exemplo prático: número de likes
Agora pense em likes de um post.
Se o número correto é 101, mas por alguns segundos aparece 100, provavelmente está tudo bem.
Nesse caso, o sistema pode tolerar alguma inconsistência.
Mas mesmo aqui, Uncommitted Read ainda não costuma ser uma boa ideia.
Normalmente, o caminho seria aceitar eventual consistency, cache ou processamento assíncrono.
Não necessariamente ler dado sujo.
Existe uma diferença entre:
dado levemente atrasado
e
dado que nunca foi confirmado
Essa diferença é muito importante.
Dado atrasado não é a mesma coisa que dado sujo
Esse ponto é fácil de confundir.
Quando usamos cache, read replica ou processamento assíncrono, podemos ter dados atrasados.
Exemplo:
Valor real: 101 likes
Cache ainda mostra: 100 likes
Esse dado está atrasado.
Mas ele já foi verdadeiro em algum momento.
Agora, no dirty read:
Valor temporário: R$900
Depois houve ROLLBACK
Valor real voltou para R$1000
O valor R$900 nunca foi oficialmente confirmado.
Essa é a diferença.
Dado atrasado pode ser aceitável.
Dado sujo é muito mais perigoso.
Conclusão
Uncommitted Read é quando uma transação lê dados que ainda não foram confirmados por outra transação.
Isso pode gerar dirty reads, ou seja, leituras de dados temporários que talvez nunca sejam válidos.
Committed Read é quando uma transação só lê dados que já passaram por COMMIT.
Isso evita dirty reads e torna o sistema mais confiável.
A ideia principal é simples:
Nem toda leitura representa uma verdade oficial do banco.
Em sistemas onde consistência importa, como saldo, pagamento, estoque, reserva e pedidos, ler apenas dados commitados é essencial.
Já em sistemas onde o dado pode ficar levemente atrasado, como likes, views e rankings, talvez seja aceitável abrir mão de consistência forte.
Mas mesmo nesses casos, é importante separar duas coisas:
dado atrasado não é a mesma coisa que dado sujo.
E entender essa diferença ajuda muito na hora de desenhar sistemas mais seguros, previsíveis e escaláveis.