Voltando ao Básico: O que é Atomicidade?
Antes de falar de transaction, precisamos entender o conceito que a sustenta: atomicidade.
O átomo, na física clássica, era considerado a menor unidade indivisível da matéria. Na computação, pegamos emprestado esse conceito para dizer:
"Esse conjunto de operações é indivisível, acontece tudo, ou não acontece nada."
É exatamente isso que uma transaction garante.
A Analogia do PIX
Imagine que você vai fazer um PIX de R$100 para um amigo. Por baixo dos panos, o banco precisa executar duas operações:
1. Debitar R$100 da sua conta
2. Creditar R$100 na conta do seu amigo
Agora imagine que a operação 1 acontece, mas o sistema cai antes da operação 2. O que acontece?
- ❌ Você perdeu R$100
- ❌ Seu amigo não recebeu nada
- ❌ O dinheiro simplesmente sumiu do sistema
Isso é uma inconsistência. E é exatamente o tipo de problema que uma transaction resolve.
Com uma transaction, o banco de dados entende que essas duas operações são uma coisa só. Se a segunda falhar, a primeira é desfeita automaticamente, como se nunca tivesse acontecido, claro, isso não resolve outros problemas como disponibilidade e escalabilidade, mas aqui estou tentando explicar o funcionamento de um conceito, em um sistema real, as coisas são sempre mais complexas.
O Ciclo de Vida de uma Transaction
INÍCIO DA TRANSACTION
│
▼
[Operação 1] ──── falhou? ──→ ROLLBACK (desfaz tudo)
│
▼
[Operação 2] ──── falhou? ──→ ROLLBACK (desfaz tudo)
│
▼
[Operação N] ──── falhou? ──→ ROLLBACK (desfaz tudo)
│
▼
Tudo certo?
│
▼
COMMIT ✅ (persiste tudo)
Só existe um de dois desfechos possíveis: commit ou rollback. Nunca um estado no meio.
As 4 Propriedades ACID
Transactions seguem um conjunto de propriedades chamado ACID. Entender cada uma delas é entender por que transactions funcionam:
| Propriedade | Nome | O que garante |
|---|---|---|
| A | Atomicidade | Tudo ou nada |
| C | Consistência | O banco sempre vai de um estado válido para outro estado válido |
| I | Isolamento | Transactions paralelas não se interferem |
| D | Durabilidade | Após o commit, os dados sobrevivem a falhas |
💡 Quando um banco de dados diz que é ACID-compliant, ele está dizendo que respeita todas essas propriedades. PostgreSQL, MySQL e SQL Server são exemplos clássicos.
Isolamento — A Propriedade Mais Traiçoeira
O Isolamento merece atenção especial, porque é onde a maioria dos bugs sutis aparecem.
Imagine dois usuários comprando o último item do estoque ao mesmo tempo:
Transaction A Transaction B
─────────────────────────────────────────────────
Lê estoque: 1 unidade
Lê estoque: 1 unidade
Decrementa: 0 unidades
Decrementa: 0 unidades ← PROBLEMA
Commit ✅
Commit ✅ ← vendeu algo que não existe
Isso se chama Race Condition, e é um dos problemas que o nível de isolamento da transaction controla. Bancos de dados oferecem diferentes níveis:
| Nível | Proteção |
|---|---|
READ UNCOMMITTED |
Nenhuma — lê dados ainda não commitados |
READ COMMITTED |
Lê apenas dados já commitados |
REPEATABLE READ |
Garante que releituras retornam o mesmo valor |
SERIALIZABLE |
Máxima proteção — transactions executam como se fossem sequenciais |
Na Prática: E-commerce com Sequelize
Agora que o conceito está claro, veja como isso se traduz em código:
const { sequelize, Pedido, Estoque, Sale } = require('./models');
async function finalizarCompra(usuarioId, produtoId, quantidade) {
// 1. Abre a transaction — a partir daqui, tudo é atômico
const transaction = await sequelize.transaction();
try {
// 2. Lê o estoque dentro da transaction
const produto = await Estoque.findOne(
{ where: { produtoId } },
{ transaction } // ← vincula essa query à transaction
);
if (!produto || produto.quantidade < quantidade) {
throw new Error('Estoque insuficiente');
}
// 3. Decrementa o estoque
await produto.update(
{ quantidade: produto.quantidade - quantidade },
{ transaction }
);
// 4. Cria o pedido
const pedido = await Pedido.create(
{ usuarioId, produtoId, quantidade, status: 'confirmado' },
{ transaction }
);
// 5. Registra a venda
await Sale.create(
{ pedidoId: pedido.id, valor: produto.preco * quantidade },
{ transaction }
);
// 6. Tudo certo — persiste as alterações
await transaction.commit();
} catch (error) {
// 7. Algo falhou — desfaz TUDO
await transaction.rollback();
throw error;
}
}
O que acontece se Sale.create falhar?
- O estoque volta ao valor original ✅
- O pedido é removido ✅
- Nenhuma inconsistência no banco ✅
O Erro Clássico: O Hook Invisível
Aqui está o cenário que quebra sistemas de desenvolvedores experientes. Você escreve uma transaction perfeita, testa, e o sistema entra em deadlock. Você olha para todo o código e não acha o problema.
O culpado? Um hook no model que ninguém lembrou de vincular à transaction.
// ⚠️ Hook disparando FORA da transaction
Pedido.afterCreate(async (pedido) => {
await LogAuditoria.create({
entidade: 'Pedido',
entidadeId: pedido.id,
acao: 'criado',
});
// Essa query não sabe que existe uma transaction aberta
// Ela tenta acessar o registro do pedido, que ainda está "preso"
// no lock da transaction — e aí o deadlock acontece
});
Por que o deadlock acontece?
Transaction principal Hook (conexão separada)
──────────────────────────────────────────────────────
Cria Pedido (lock no registro)
Tenta ler o Pedido ← bloqueado pelo lock
Aguarda o hook terminar ←──── Aguarda a transaction liberar o lock
│ │
└──────── DEADLOCK ────────────┘
(espera infinita)
✅ A Correção
Pedido.afterCreate(async (pedido, options) => {
await LogAuditoria.create(
{
entidade: 'Pedido',
entidadeId: pedido.id,
acao: 'criado',
},
{ transaction: options.transaction } // ← participa da mesma transaction
);
});
Agora o hook faz parte da transaction. Se houver rollback, o log também é desfeito. Se houver commit, tudo é salvo junto.
Resumo Mental
Pense em uma transaction como uma caixa de vidro. Tudo que acontece dentro dela é visível apenas para ela mesma, até que você decida abrir a caixa (commit) ou jogar tudo fora (rollback). Qualquer coisa que aconteça fora da caixa, enquanto ela ainda está fechada, não enxerga o que está dentro, e é aí que os problemas aparecem.
No próximo nível: quando um único banco não é suficiente e você precisa de transactions distribuídas entre microsserviços, aí entram patterns como Saga e Outbox. Mas isso é história para outro artigo.
Top comments (0)