O problema: operações pela metade
Imagine uma transferência bancária: subtrair R$ 100 da conta A e somá-los à conta B. São duas escritas. O que acontece se o sistema falhar entre as duas? O dinheiro desaparece: foi subtraído de A mas nunca chegou a B.
Uma transação resolve isso: agrupa várias operações em uma unidade indivisível que é executada inteira ou nada.
BEGIN, COMMIT, ROLLBACK
BEGIN;
UPDATE contas SET saldo = saldo - 100 WHERE id = 'A';
UPDATE contas SET saldo = saldo + 100 WHERE id = 'B';
COMMIT; -- confirma: ambas as mudanças se tornam permanentes de uma vez
BEGIN(ouBEGIN TRANSACTION) abre a transação.COMMITa confirma: todas as mudanças são aplicadas de uma vez.ROLLBACKa desfaz: descarta todas as mudanças feitas desdeBEGIN, como se nada tivesse acontecido.
BEGIN;
UPDATE contas SET saldo = saldo - 100 WHERE id = 'A';
-- algo dá errado (saldo insuficiente, erro de validação...)
ROLLBACK; -- nada é salvo; o saldo de A fica intacto
ACID: as quatro garantias
As transações de um banco de dados relacional cumprem o ACID:
- A — Atomicidade (Atomicity). Tudo ou nada. Se uma operação da transação falha, todas são desfeitas. Nunca fica pela metade.
- C — Consistência (Consistency). A transação leva o banco de dados de
um estado válido a outro estado válido: as regras são respeitadas
(chaves estrangeiras, restrições
CHECK,UNIQUE...). Nunca se viola uma invariante dos dados. - I — Isolamento (Isolation). As transações concorrentes não se
atropelam: cada uma é executada como se estivesse sozinha. Os resultados
intermediários de uma não são visíveis para as outras até o
COMMIT. - D — Durabilidade (Durability). Uma vez feito o
COMMIT, as mudanças persistem mesmo que a luz caia logo depois. Foram escritas em disco (normalmente por meio de um write-ahead log).
Níveis de isolamento
O isolamento perfeito é caro (obriga a serializar tudo). Por isso o SQL define níveis que equilibram correção e desempenho, do menor ao maior grau de garantia:
| Nível | Permite | Risco que evita |
|---|---|---|
| Read Uncommitted | ler mudanças não confirmadas (dirty reads) | o menos seguro |
| Read Committed | apenas ler dados já confirmados | dirty reads |
| Repeatable Read | releituras consistentes dentro da transação | non-repeatable reads |
| Serializable | como se as transações fossem em série | phantom reads (o mais seguro) |
Quanto mais alto o nível, mais correto, porém menos concorrente (mais bloqueios).
Condições de corrida (race conditions)
Sem isolamento adequado surgem condições de corrida: duas transações concorrentes leem o mesmo dado, o modificam e uma sobrescreve a outra (lost update).
Exemplo clássico — reservar o último ingresso de um show:
- Transação T1 lê
ingressos = 1. - Transação T2 lê
ingressos = 1(ainda não foi confirmado nada!). - T1 subtrai 1 → escreve
0. - T2 subtrai 1 → escreve
0.
Resultado: foram vendidos dois ingressos, mas só havia um. As
transações com o nível de isolamento adequado (ou um bloqueio explícito como
SELECT ... FOR UPDATE) evitam esse erro.
Regra mental: uma transação deve ser correta mesmo que outra seja executada ao mesmo tempo.
Exemplos
Transação confirmada com COMMIT (transferência)
BEGIN;
UPDATE pedidos SET estado = 'pago' WHERE id = 2;
UPDATE pedidos SET estado = 'pago' WHERE id = 6;
COMMIT;
SELECT id, estado FROM pedidos WHERE id IN (2, 6) ORDER BY id;
ROLLBACK desfaz as mudanças da transação
BEGIN;
UPDATE pedidos SET total = 0 WHERE id = 1;
ROLLBACK;
-- O total do id=1 continua sendo o original (120.0)
SELECT id, total FROM pedidos WHERE id = 1;