MongoDB: Processamento de transações
Última atualização: 2026-08-26
As transações garantem a atomicidade das operações envolvendo vários documentos — o domínio desse conceito permite o desenvolvimento de sistemas financeiros e de pedidos confiáveis.
1. O que você vai aprender
- Transações com múltiplos documentos no MongoDB 4.0+
- session.startTransaction / commitTransaction
- Propriedades do ACID
- readConcern / writeConcern / readPreference
- Consistência causal
- Wrappers de transação do Mongoose
sequenceDiagram
participant App as Applications
participant DB as MongoDB<br/>Dungeon Collection
participant Log as Log
App->>DB: startTransaction()
activate DB
DB-->>App: session
App->>DB: Deduct $100
DB-->>App: OK
App->>DB: Add $100
DB-->>App: OK
App->>DB: Create a Transaction Log
DB-->>App: OK
alt All successful
App->>DB: commitTransaction()
DB-->>App: ✅ Committed
DB->>Log: Persistence
else Any failure
App->>DB: abortTransaction()
DB-->>App: ❌ Rollback
Note over DB: All changes have been reversed<br/>Data is rolled back to the state before the transaction
end
deactivate DB
2. Por que as transações são necessárias?
Explicação do conceito: Uma transação é uma unidade lógica de operações de banco de dados que garante que todas as operações nela contidas sejam totalmente bem-sucedidas ou totalmente revertidas. Ao gravar em vários documentos ou coleções (como em transferências de fundos ou na realização de pedidos), a consistência dos dados não pode ser garantida sem transações — o sucesso parcial e a falha parcial resultariam em dados inconsistentes.
Como funciona: O MongoDB 4.0 e versões posteriores oferecem suporte a transações ACID com múltiplos documentos, implementadas por meio do isolamento de instantâneo com base no mecanismo WiredTiger. Quando uma transação é iniciada, um instantâneo é criado, e todas as operações de leitura e gravação são realizadas em relação a esse instantâneo; no momento do commit, as alterações são aplicadas de forma atômica aos arquivos de dados e, no momento do rollback, todas as alterações são descartadas. Nos bastidores, as transações dependem do oplog do conjunto de réplicas para garantir a persistência e a replicação.
Uma explicação detalhada dos princípios ACID:
| Recurso | Significado | Implementação no MongoDB | Princípio |
|---|---|---|---|
| Atomicidade | As transações são totalmente bem-sucedidas ou totalmente malsucedidas | commit / abort | O WiredTiger garante isso por meio do log de reversão: gravações atômicas durante o commit e recuperação usando o log de reversão durante a reversão |
| Consistência | As restrições de integridade dos dados permanecem inalteradas | Validação do esquema + restrições de transação | Todas as restrições são verificadas antes do commit de uma transação; se alguma for violada, a transação é rejeitada |
| Isolamento | As transações simultâneas não interferem umas nas outras | Isolamento por instantâneo | É criado um instantâneo dos dados no início de uma transação; todas as leituras e gravações se baseiam nesse instantâneo ao longo da transação, e a transação não é afetada por outras transações |
| Durabilidade | Armazenado permanentemente após a confirmação da transação | Diário + oplog do conjunto de réplicas | As gravações são primeiro registradas no diário (WAL) e, em seguida, replicadas para a maioria dos nós do conjunto de réplicas |
Mecanismo MVCC: O MongoDB implementa o isolamento por snapshot por meio do Controle de Concorrência Multiversão (MVCC). Cada documento mantém várias versões históricas; as transações leem os dados da versão correspondente ao seu carimbo de data/hora de início, e as operações de gravação criam novas versões sem sobrescrever as antigas. A nova versão só se torna visível para outras transações após a confirmação.
graph TB
subgraph "MVCC Multiple Versions"
D1["Document v1<br/>balance: 1000"]
D2["Document v2<br/>balance: 900<br/>(Transaction A Edit)"]
D3["Document v3<br/>balance: 1100<br/>(Transaction B Edit)"]
end
subgraph "Transaction Snapshot Read"
T1["TransactionsA (t1)<br/>Read v1"] --> R1["balance: 1000"]
T2["TransactionsB (t2)<br/>Read v1"] --> R2["balance: 1000"]
end
D1 --> D2
D1 --> D3
style D2 fill:#cce5ff
style D3 fill:#d4edda
Casos de uso:
- Transferências financeiras (os saques e depósitos devem ser atômicos)
- Realização de pedidos no comércio eletrônico (Pedido + Dedução do estoque + Dedução do saldo + Registro)
- Atualização por junção de várias tabelas (sincronização das tabelas de usuários e funções)
- Não é adequado para: operações com um único documento (os documentos individuais do MongoDB são atômicos por natureza), transações curtas de alta frequência (as transações acarretam uma sobrecarga significativa)
// ❌ Counterexample: Transfer without a transaction
async function transfer(fromUserId, toUserId, amount) {
await User.updateOne({ _id: fromUserId }, { $inc: { balance: -amount } });
// System Crash!
await User.updateOne({ _id: toUserId }, { $inc: { balance: amount } });
// The user's balance was deducted, but the recipient did not receive the payment
}
3. Noções básicas sobre transações
Explicação do conceito: As transações no MongoDB são gerenciadas por meio de objetos Session — startSession() cria uma sessão, startTransaction() inicia uma transação, commitTransaction() confirma e abortTransaction() reverte. Todas as operações dentro de uma transação devem ser passadas como parâmetros de { session }.
Como funciona: O ciclo de vida completo de uma transação é o seguinte: iniciar sessão → iniciar transação → executar operações (com parâmetros de sessão) → confirmar/reverter → encerrar sessão. Ao confirmar, o WiredTiger grava de forma atômica todas as alterações no diário; ao reverter, ele reverte todas as alterações usando o log de reversão. O tempo limite padrão da transação é de 60 segundos; as transações são revertidas automaticamente se ultrapassarem esse tempo limite.
Ciclo de vida da transação:
stateDiagram-v2
[*] --> StartSession: startSession()
StartSession --> Active: startTransaction()
Active --> Active: Perform an action (with session)
Active --> Committed: commitTransaction()
Active --> Aborted: abortTransaction()
Committed --> [*]: endSession()
Aborted --> [*]: endSession()
note right of Active: Default 60s automatic rollback on timeout
note right of Committed: Persist changes to journal
Regras gramaticais:
| Etapa | Método | Descrição |
|---|---|---|
| 1 | startSession() |
Criar uma sessão |
| 2 | startTransaction() |
Iniciar transação |
| 3 | Operação + {session} |
Todas as operações de leitura e gravação devem passar pela sessão |
| 4a | commitTransaction() |
Tudo correu bem → Enviar |
| 4b | abortTransaction() |
Qualquer falha → Revertida |
| 5 | endSession() |
Liberar recursos da sessão |
// === MongoDB 4.0+ Multi-document transactions ===
const session = db.getMongo().startSession();
session.startTransaction();
try {
// 1. Deduct
db.users.updateOne(
{ _id: fromUserId },
{ $inc: { balance: -amount } },
{ session }
);
// 2. Add
db.users.updateOne(
{ _id: toUserId },
{ $inc: { balance: amount } },
{ session }
);
// 3. Commit Transaction
await session.commitTransaction();
} catch (err) {
// 4. Rollback
await session.abortTransaction();
throw err;
} finally {
session.endSession();
}
Análise dos pontos-chave:
- A operação para anular a transmissão de
{ session }não faz parte de uma transação e não é protegida por ela. commitTransactioneabortTransactionsão operações idempotentes; chamá-las repetidamente não resultará em erro.- As transações são automaticamente revertidas ao atingir o tempo limite; a camada de aplicação deve definir um tempo limite razoável e implementar uma lógica de repetição de tentativas.
4. Propriedades do ACID
Visão geral do conceito: ACID refere-se às quatro garantias fundamentais das transações em bancos de dados — Atomicidade, Consistência, Isolamento e Durabilidade. Compreender como o ACID é implementado no MongoDB é a base para projetar um sistema transacional confiável.
Explicação detalhada dos níveis de isolamento: O MongoDB oferece suporte a três níveis de isolamento de leitura, que são controlados por meio de readConcern:
| Nível de isolamento | readConcern | Comportamento | Caso de uso |
|---|---|---|---|
| Ler dados não enviados | local |
Ler os dados locais mais recentes (pode ocorrer reversão) | Padrão, prioridade no desempenho |
| Leitura confirmada | majority |
Leitura de dados que foram confirmados pela maioria | Requisito de consistência forte |
| Isolamento por instantâneo | snapshot |
Instantâneo com consistência de leitura dentro da transação | Padrão dentro da transação |
sequenceDiagram
participant T1 as Transactions1
participant T2 as Transactions2
participant DB as MongoDB
Note over DB: Initial balance=1000
T1->>DB: startTransaction(readConcern: snapshot)
T1->>DB: Read balance → 1000
T2->>DB: startTransaction()
T2->>DB: balance -100 → Write 900
T2->>DB: commitTransaction()
T1->>DB: Read balance → 1000 (Snapshot isolation, can't see T2 changes)
Note over T1: Snapshots ensure intra-transaction consistency
T1->>DB: commitTransaction()
Note over DB: Conflict detection → If T1 also modifies balance, an error will be reported
| Recurso | Significado | Implementação no MongoDB |
|---|---|---|
| Atomicidade | A transação é totalmente bem-sucedida ou totalmente malsucedida | commit / abort |
| Consistência | Restrições de integridade de dados | Validação de esquema + transações |
| Isolamento | As transações simultâneas não interferem umas nas outras | Isolamento por instantâneo |
| Durabilidade | Armazenado permanentemente após a confirmação da transação | Diário + conjunto de réplicas |
5. readConcern / writeConcern / readPreference
Explicação do conceito: Essas três configurações formam a “trindade” do controle de consistência de transações do MongoDB, regendo, respectivamente, a consistência de leitura, a durabilidade de gravação e as políticas de roteamento de leitura. Elas determinam o equilíbrio entre consistência e desempenho nas transações.
Como funciona:
- writeConcern: O número de nós que devem confirmar uma operação de gravação para que ela seja considerada bem-sucedida.
w: majorityGarante que as gravações não sejam perdidas - readConcern: A versão dos dados visualizada por uma operação de leitura.
majorityGarante que as leituras sejam confirmadas;snapshotgarante a consistência intra-transação. - readPreference: Para qual nó as operações de leitura são encaminhadas.
primaryConsistência máxima,secondaryReduz a carga no nó primário
(1) Preocupação com a redação
Princípio: Após uma operação de gravação ser registrada no Primário, ela deve aguardar um número especificado de confirmações do Secundário de que os dados foram replicados antes de retornar um resultado de sucesso.
| Parâmetro | Valor | Comportamento | Consistência | Desempenho |
|---|---|---|---|---|
w |
1 | Apenas confirmação primária | Baixa | Mais rápida |
w |
maioria | confirmado pela maioria dos nós | alto | lento |
j |
true | Gravar no diário do disco | Mais rápido | Mais lento |
wtimeout |
ms | Tempo limite de espera | — | Erro de tempo limite |
session.startTransaction({
writeConcern: {
w: 'majority', // Confirmed by a majority of nodes
j: true, // Write to disk journal
wtimeout: 5000 // 5 Timeout in seconds
}
});
(2) Preocupação com a leitura
Princípio: Determina quais versões dos dados uma operação de leitura pode acessar — se a versão local mais recente (que pode não ter sido confirmada) ou a versão com o maior número de confirmações (que já foi confirmada).
| Nível | Descrição | Cenários aplicáveis |
|---|---|---|
local |
Lê os dados locais mais recentes (padrão) | Orientado para o desempenho; permite a leitura de dados não confirmados |
majority |
Ler dados que já foram amplamente confirmados | Consistência forte |
snapshot |
Isolamento por instantâneo (apenas dentro de uma transação) | Padrão para transações; evita leituras fantasmas |
session.startTransaction({
readConcern: {
level: 'majority' // Read Submitted Data
},
writeConcern: { w: 'majority' }
});
(3) Ler preferências
Princípio: Determina se as operações de leitura são encaminhadas para o nó primário ou secundário, implementando assim a separação entre leitura e gravação.
| Padrão | Comportamento | Cenários aplicáveis |
|---|---|---|
primary |
Nó primário somente leitura | Transações fortemente consistentes |
primaryPreferred |
Priorizar o primário; se indisponível, recorrer ao secundário | Cenários gerais |
secondary |
Nó secundário somente para leitura | Relatórios/análise, alívio da carga do nó primário |
secondaryPreferred |
Priorizar a leitura; recorrer ao primário caso este não esteja disponível | Predominância de leitura, poucas gravações |
nearest |
Menor latência de rede | Cluster geograficamente distribuído |
session.startTransaction({
readPreference: 'primary' // Read-Only Primary Node
});
session.startTransaction({
readPreference: 'secondary' // Read from a child node
});
session.startTransaction({
readPreference: 'secondaryPreferred' // Priority Node
});
Conjuntos de três peças recomendados:
| Cenário | writeConcern | readConcern | readPreference |
|---|---|---|---|
| Transações financeiras | maioria + j:true | instantâneo | primário |
| Transações gerais | maioria | maioria | principal |
| Análise do relatório | — | local | secundário |
| Desenvolvimento e Testes | w:1 | local | primaryPreferred |
6. Encapsulamento de transações no Mongoose
Explicação do conceito: O Mongoose oferece uma API de transações mais elegante — os parâmetros startSession() e session. No entanto, o código padrão do tipo “try-catch-commit-abort”, usado para gerenciar transações manualmente, é prolixo; encapsulá-lo na função utilitária withTransaction pode simplificar significativamente o código da lógica de negócios.
Como funciona: O wrapper withTransaction do Mongoose automatiza o gerenciamento do ciclo de vida da sessão (início → confirmação/cancelamento → fim), permitindo que as funções de negócios se concentrem exclusivamente na lógica principal. O Mongoose também oferece suporte à passagem de parâmetros { session } para operações de modelo (findById, create, updateOne), possibilitando a integração perfeita de operações CRUD dentro de transações.
Comparação de padrões de encapsulamento de transações:
| Padrão | Tamanho do código | Tratamento de erros | Suporte à repetição de tentativas | Casos de uso |
|---|---|---|---|---|
| Try-catch manual | Múltiplo | Manual | Nenhum | Cenários simples |
| Encapsulamento com transação | Poucos | Automático | Pode ser adicionado | Recomendado para produção |
| mongoose.connection.transaction | Mínimo | Automático | Integrado | Mongoose 6+ |
// === mongoose Transaction Encapsulation ===
async function withTransaction(callback) {
const session = await mongoose.startSession();
session.startTransaction();
try {
const result = await callback(session);
await session.commitTransaction();
return result;
} catch (err) {
await session.abortTransaction();
throw err;
} finally {
session.endSession();
}
}
// === Usage: Transfer ===
async function transfer(fromUserId, toUserId, amount) {
return withTransaction(async (session) => {
const fromUser = await User.findById(fromUserId).session(session);
if (fromUser.balance < amount) {
throw new Error('Insufficient balance');
}
await User.updateOne(
{ _id: fromUserId },
{ $inc: { balance: -amount } },
{ session }
);
await User.updateOne(
{ _id: toUserId },
{ $inc: { balance: amount } },
{ session }
);
await TransactionLog.create([{
fromUserId,
toUserId,
amount,
createdAt: new Date()
}], { session });
return { success: true };
});
}
▶ Exemplo 2: Encapsulamento da repetição de transações no Mongoose
// Alice's ShopHub Financial System: Transaction encountered WriteConflict automatic retry
async function withRetryTransaction(callback, maxRetries = 3) {
let lastError;
for (let i = 0; i < maxRetries; i++) {
const session = await mongoose.startSession();
session.startTransaction({
readConcern: { level: 'snapshot' },
writeConcern: { w: 'majority' }
});
try {
const result = await callback(session);
await session.commitTransaction();
return result;
} catch (err) {
await session.abortTransaction();
lastError = err;
if (err.errorLabels && err.errorLabels.includes('TransientTransactionError')) {
console.log(`Retry ${i + 1}/${maxRetries} due to WriteConflict`);
continue;
}
throw err;
} finally {
session.endSession();
}
}
throw lastError;
}
// Usage
await withRetryTransaction(async (session) => {
await User.updateOne({ _id: fromId }, { $inc: { balance: -100 } }, { session });
await User.updateOne({ _id: toId }, { $inc: { balance: 100 } }, { session });
});
7. Limites de transações
Explicação do conceito: As transações do MongoDB têm limites de uso bem definidos — elas devem ser realizadas dentro de um conjunto de réplicas, estão sujeitas a limites de tamanho e não suportam determinadas operações. Compreender essas limitações é fundamental para evitar incidentes em produção.
Explicação detalhada das restrições:
| Restrição | Descrição | Motivo | Estratégia de mitigação |
|---|---|---|---|
| São necessários conjuntos de réplicas | O MongoDB autônomo não oferece suporte a transações | As transações dependem do oplog para persistência | É permitido um conjunto de réplicas de nó único em ambientes de desenvolvimento |
| Documento de 16 MB | Total de todas as operações dentro de uma transação | Limite de um único documento do WiredTiger | Divisão de transações grandes em transações menores |
| Tempo limite padrão de 60 segundos | maxTransactionLockRequestTimeoutMillis | Evitar que transações demoradas mantenham bloqueios | Ajustar o parâmetro de tempo limite |
| Não é possível realizar operações em uma coleção com limite | Restrições parciais | Não é possível reverter transações em coleções com limite | Evite realizar operações em coleções com limite dentro de transações |
| Não é possível criar conjuntos dentro de uma transação | Restrição parcial (flexibilizada na versão 4.4+) | Conflito entre DDL e transações | Crie os conjuntos antes da transação |
| Conflitos de gravação | Modificações simultâneas no mesmo documento | Mecanismo de bloqueio otimista | Nova tentativa automática em caso de TransientTransactionError |
| Espera por bloqueio | Transações longas bloqueiam outras operações | Bloqueio de intenção de gravação | Reduza a duração das transações para evitar operações demoradas |
Impacto no desempenho das transações:
| Operação | Não transacional | Dentro de uma transação | Motivo da sobrecarga |
|---|---|---|---|
| Gravação em um único documento | Teste de desempenho | +30–50% | Manutenção de instantâneos + gerenciamento de bloqueios |
| Gravação em vários documentos | N operações independentes de E/S | 1 confirmação | A fusão de transações em uma única operação de E/S pode, na verdade, ser mais rápida |
| Leitura | Linha de base | +10–20% | Sobrecarga adicional para leituras de instantâneos |
| Enviar | — | 5–50 ms | fsync do diário + gravação no oplog |
Melhores práticas para transações:
| Exercício | Descrição |
|---|---|
| Mantenha as transações o mais curtas possível | Evite transações longas que mantenham bloqueios; mantenha-as abaixo de 100 ms |
| Evite cálculos dentro de transações | Realize cálculos complexos fora das transações; limite as transações apenas a operações de leitura e gravação |
| Repetição de tentativas em casos de conflitos de gravação | O MongoDB 4.0+ oferece o errorLabels para identificar erros que podem ser repetidos |
| Priorizar operações atômicas em um único documento | updateOne + $inc é atômico por si só; não é necessária nenhuma transação |
8. Consistência causal
Explicação do conceito: A consistência causal é um modelo de consistência mais leve do que a consistência forte — ela não garante que todas as operações sejam ordenadas globalmente, mas garante que as operações com relações causais sejam executadas na ordem correta. Por exemplo, “ler o saldo primeiro e, em seguida, deduzir o valor” — a operação de dedução deve se basear no saldo lido mais recentemente; essa é uma dependência causal.
Como funciona: O MongoDB alcança a consistência causal por meio de operationTime e clusterTime. Cada operação dentro de uma sessão herda o logicalTime da operação anterior, e o servidor garante que as operações subsequentes vejam os resultados das operações anteriores. Para habilitar a consistência causal, é necessário readConcern: majority + writeConcern: majority.
Consistência causal versus outros modelos de consistência:
| Modelo | Garantia | Desempenho | Aplicabilidade |
|---|---|---|---|
| Consistência forte (linearizável) | Ordenação global | Mais lenta | Núcleo financeiro |
| Consistência causal | Ordem causal | Relativamente rápido | Operações em várias etapas |
| Consistência eventual | Não ordenado | Mais rápido | Registros, notificações |
| Ler minhas postagens | Minhas postagens estão visíveis | Rápido | Experiência do usuário |
sequenceDiagram
participant A as Alice
participant P as Primary
participant S as Secondary
A->>P: Read Balance (readConcern: majority)
P-->>A: balance=1000, clusterTime=t1
A->>P: Deduct $100 (writeConcern: majority)
Note over A,P: Carry afterClusterTime=t1
P->>S: Copy oplog
S-->>P: Confirm
P-->>A: OK, clusterTime=t2
A->>P: View Transaction History (readConcern: majority)
Note over A,P: Carry afterClusterTime=t2
P-->>A: Includes records of payments that have just been deducted ✅
Note over A,P: Consistency of Cause and Effect:If you read it, you're sure to recognize your own previous writing.
// === Causal Consistency:Ensure the Correct Order of Operations ===
const session = db.getMongo().startSession();
session.startTransaction({
readConcern: { level: 'majority' },
writeConcern: { w: 'majority' }
});
// Operation 1:Read the current balance
const account = db.accounts.findOne({ userId: 'user_001' }, { session });
// Operation 2:Write Based on Read Results
db.accounts.updateOne(
{ userId: 'user_001' },
{ $set: { balance: account.balance - 100 } },
{ session }
);
// Guarantee:Operation 2 What you see is the operation 1 Subsequent Status
Análise dos pontos-chave:
- Para garantir a consistência causal, é necessário usar uma sessão, e tanto
readConcernquantowriteConcerndevem ser definidos comomajority. - Ao realizar leituras entre nós (readPreference: secondary), a consistência causal garante que a leitura reflita a gravação feita pelo nó local.
- A consistência causal é o mecanismo subjacente às transações com múltiplos documentos e aos Change Streams do MongoDB.
▶ Exemplo: E-commerce Order com Payment e Inventory Deduction (Difficulty ⭐⭐)
// Scene: ShopHub checkout - reserve inventory, create order, deduct payment atomically
const mongoose = require('mongoose');
const session = await mongoose.startSession();
session.startTransaction();
try {
const Product = mongoose.model('Product');
const Order = mongoose.model('Order');
const User = mongoose.model('User');
const userId = 'user_001';
const items = [
{ productId: 'prod_001', sku: 'PHONE-001', qty: 2, price: 599 },
{ productId: 'prod_002', sku: 'BOOK-001', qty: 1, price: 29 }
];
const totalAmount = 1227;
// Step 1: Check and reserve inventory (with pessimistic lock)
for (const item of items) {
const product = await Product.findOneAndUpdate(
{ _id: item.productId, stock: { $gte: item.qty } },
{ $inc: { stock: -item.qty } },
{ session, new: true }
);
if (!product) {
throw new Error(`Insufficient stock for ${item.sku}`);
}
}
// Step 2: Deduct user balance
const user = await User.findOneAndUpdate(
{ _id: userId, balance: { $gte: totalAmount } },
{ $inc: { balance: -totalAmount } },
{ session, new: true }
);
if (!user) {
throw new Error('Insufficient balance');
}
// Step 3: Create order
const order = await Order.create([{
userId,
items,
total: totalAmount,
status: 'paid',
paidAt: new Date()
}], { session });
await session.commitTransaction();
console.log('Order created:', order[0]._id);
} catch (err) {
await session.abortTransaction();
console.error('Transaction failed:', err.message);
} finally {
session.endSession();
}
Saída:
TEXT 📖 Somente leituraOrder created: 67890abcdef12345 // OR on failure: Transaction failed: Insufficient stock for PHONE-001
▶ Exemplo: Um guia prático completo sobre transações de pedidos no comércio eletrônico
// Scene: Order placement process (Order + Inventory Deduction + Wallet Deduction + Logging), Fully Atomic
// Introduction: A replica set is required. Transactions must be in progress
// Initialize Data
db.products.insertOne({ sku: 'PHONE-001', stock: 10, price: 599 });
db.users.insertOne({ _id: 'user_001', balance: 1000 });
db.transaction_logs.createIndex({ userId: 1, createdAt: -1 });
// Complete Transaction Functions
async function placeOrder(userId, items) {
const session = db.getMongo().startSession();
session.startTransaction({
readConcern: { level: 'snapshot' },
writeConcern: { w: 'majority' }
});
try {
// 1. Calculate the total amount + Check Inventory (Atomic Read)
let total = 0;
for (const item of items) {
const product = db.products.findOne(
{ sku: item.sku, stock: { $gte: item.qty } },
{ session }
);
if (!product) {
throw new Error(`Out of Stock: ${item.sku}`);
}
total += product.price * item.qty;
}
// 2. Check User Balance
const user = db.users.findOne({ _id: userId }, { session });
if (user.balance < total) {
throw new Error('Insufficient balance');
}
// 3. Inventory Deduction (Conditional, Preventing Overselling)
for (const item of items) {
const result = db.products.updateOne(
{ sku: item.sku, stock: { $gte: item.qty } },
{ $inc: { stock: -item.qty } },
{ session }
);
if (result.modifiedCount === 0) {
throw new Error(`Inventory deduction failed: ${item.sku}`);
}
}
// 4. Deduct from the user's balance
db.users.updateOne(
{ _id: userId, balance: { $gte: total } },
{ $inc: { balance: -total } },
{ session }
);
// 5. Create an Order
const orderResult = db.orders.insertOne({
userId,
items,
total,
status: 'paid',
createdAt: new Date()
}, { session });
// 6. Record the transaction log
db.transaction_logs.insertOne({
userId,
orderId: orderResult.insertedId,
amount: total,
type: 'purchase',
createdAt: new Date()
}, { session });
// 7. Commit Transaction
session.commitTransaction();
return { success: true, orderId: orderResult.insertedId };
} catch (err) {
// Any failure → Roll Back All
session.abortTransaction();
return { success: false, error: err.message };
} finally {
session.endSession();
}
}
// Execute: Place an Order
placeOrder('user_001', [
{ sku: 'PHONE-001', qty: 1 }
]);
// Testing Rollback Scenarios: Deliberately Creating Errors
placeOrder('user_001', [
{ sku: 'NONEXIST', qty: 1 } // The product does not exist.
]);
// Throw an exception → Transaction Rollback → Inventory, balance, order, all logs remain unchanged
// Verifying Atomicity:
// db.products.findOne({ sku: 'PHONE-001' }) → stock: 10 (Not deducted)
// db.users.findOne({ _id: 'user_001' }) → balance: 1000 (Not deducted)
Resultado: Quando uma transação é bem-sucedida, todas as alterações são confirmadas de uma só vez; quando uma transação falha, todas as alterações são revertidas, garantindo a consistência dos dados.
❓ Perguntas Frequentes
P: Uma transação pode garantir consistência de dados forte? R: Com um conjunto de réplicas + writeConcern maioria + readConcern maioria + readPreference primário, sim.
P: Em quanto o desempenho das transações fica prejudicado? R: É 30 a 50% mais lento do que as operações não transacionais. As transações envolvem bloqueios e instantâneos.
P: Uma instância do MongoDB com um único nó pode usar transações? R: Não. É necessário que seja um conjunto de réplicas ou um cluster fragmentado.
📖 Resumo
- As transações com múltiplos documentos no MongoDB 4.0 ou superior exigem um conjunto de réplicas
- session.startTransaction / commit / abort
- Propriedades ACID: Atomicidade / Consistência / Isolamento / Durabilidade
- A trindade: readConcern / writeConcern / readPreference
- Consistência causal
- Wrappers de transação do Mongoose
📝 Exercícios
- Questão básica (⭐): Implemente uma transação de transferência usando o mongosh (incluindo try-catch e rollback).
- Problema básico (⭐): Use o Mongoose para encapsular a função utilitária
withTransaction. - Exercício avançado (⭐⭐): Implemente uma transação de pedido (fazer o pedido + deduzir do estoque + criar o pedido + esvaziar o carrinho de compras — tudo de forma atômica).
- Exercício avançado (⭐⭐): Teste a reversão da transação em caso de falha (gerar um erro intencionalmente para verificar a atomicidade).
- Desafio (⭐⭐⭐): Desenvolva um sistema completo de transações de comércio eletrônico (pedidos + estoque + carteira + registros) que suporte a reversão distribuída.