MongoDB: Padrões avançados do Mongoose
Última atualização: 2026-08-26
Padrões avançados do Mongoose — Domine recursos avançados como populate, discriminator, lean e aggregate.
1. O que você vai aprender
- Preencher consultas de junção (em comparação com $lookup)
- discriminador
- Otimização de desempenho da função
lean() - Pipeline de agregação Model.aggregate()
- Declaração de índice de esquema
- Dicas de otimização de desempenho para o Mongoose 7.x
graph TB
A[mongoose Advanced Features] --> B[populate<br/>Application Layer JOIN]
A --> C[discriminator<br/>Single Set, Multiple Schema]
A --> D[lean<br/>Performance Optimization]
A --> E[aggregate<br/>Database-Level Aggregation]
A --> F[Schema Index<br/>Declarative]
B --> B1[Multiple queries<br/>N+1 Risks]
C --> C1[role Field<br/>Classification by Type]
D --> D1[Skip hydrate<br/>↑5x Performance]
E --> E1[$facet/$lookup<br/>Return Multiple Dimensions in a Single Operation]
style D fill:#d4edda
2. Preencher consultas com junções
O que é populate? populate é a implementação do Mongoose de um “JOIN na camada de aplicação” — quando um esquema contém um campo ref do tipo ObjectId, o populate emite automaticamente uma consulta adicional para substituir o ObjectId referenciado pelo documento completo. Essencialmente, isso envolve duas consultas: primeiro, recuperar o ObjectId do documento primário e, em seguida, recuperar os dados completos do documento associado.
Os princípios fundamentais do populate:
sequenceDiagram
participant App as Node.js Applications
participant Mongo as MongoDB
App->>Mongo: 1st query: Order.find()
Mongo-->>App: [order1, order2, ...]
App->>Mongo: 2nd query: User.find({_id: {$in: [ObjectId1, ObjectId2, ...]}})
Mongo-->>App: Back users
App->>App: Merge: order.userId → user Object
Comparação entre populate e $lookup:
| Dimensão | preencher | $lookup |
|---|---|---|
| Camada de execução | Camada de aplicação (2 ou mais consultas) | Camada de banco de dados (1 agregação) |
| Número de consultas | Risco N+1 | 1 |
| Flexibilidade | Média (suporta apenas referências a ObjectId) | Alta (associações condicionais arbitrárias) |
| Desempenho | Adequado para conjuntos de dados pequenos | Recomendado para conjuntos de dados grandes |
| Concisão do código | Alta (uma linha de .populate()) | Baixa (sintaxe do pipeline de agregação) |
| Tipo de retorno | Documento Mongoose | objeto simples |
O Problema N+1: Ao consultar 100 pedidos e usar populate em cada order.userId, isso gera 1 (consulta de pedidos) + 100 (consultas de usuários) = 101 consultas. O Mongoose otimiza isso automaticamente para uma consulta em lote $in (1+1=2 consultas), mas operações populate aninhadas ainda podem gerar consultas adicionais.
Casos de uso: Para relações um-para-um (por exemplo, recuperar o usuário associado a um pedido), use populate; para relações em massa com condições complexas, use $lookup; para fins exclusivamente de exibição, use lean combinado com populate.
(1) Preenchimento básico
// === Basic populate ===
const user = await User.findById(userId).populate('addresses');
// SELECT u.*, a.* FROM users u LEFT JOIN addresses a ON u._id = a.userId
// === Nested populate ===
const order = await Order.findById(orderId)
.populate('userId')
.populate({
path: 'items.productId',
select: 'sku title price'
});
// === Conditions populate ===
const orders = await Order.find()
.populate({
path: 'userId',
match: { isActive: true },
select: 'username avatar'
});
| populate vs $lookup | populate | $lookup |
|---|---|---|
| Camada de execução | Camada de aplicação | Camada de banco de dados |
| Número de consultas | N+1 | 1 |
| Flexibilidade | Média | Alta |
| Desempenho | Adequado para conjuntos de dados pequenos | Recomendado para conjuntos de dados grandes |
3. Discriminador
O que é um discriminador? Um discriminador é o mecanismo de “herança de coleção única” do Mongoose — vários modelos compartilham a mesma coleção do MongoDB, com os tipos de documentos diferenciados por uma chave discriminadora. Isso é semelhante à herança na programação orientada a objetos: a classe base define campos comuns, as subclasses a estendem com campos específicos e todas as instâncias residem na mesma tabela.
Mecanismo subjacente do discriminador:
graph TB
subgraph "users Gathering(Single Set)"
D1["{role: 'Customer', loyaltyPoints: 100, email: 'alice@...'}"]
D2["{role: 'Customer', loyaltyPoints: 50, email: 'bob@...'}"]
D3["{role: 'Admin', permissions: ['manage'], email: 'admin@...'}"]
end
subgraph "mongoose Model Layer"
User[User Model<br/>email + username + passwordHash]
Customer[Customer Model<br/>+ loyaltyPoints + preferredCategories]
Admin[Admin Model<br/>+ permissions + lastLoginAt]
end
User -->|discriminator| Customer
User -->|discriminator| Admin
Customer -->|Search: role='Customer'| D1
Customer -->|Search: role='Customer'| D2
Admin -->|Search: role='Admin'| D3
style User fill:#fff3cd
style Customer fill:#d4edda
style Admin fill:#cce5ff
Discriminador x Conjunto Independente:
| Dimensão | Discriminador (conjunto único) | Conjunto independente |
|---|---|---|
| Método de consulta | Filtro automático por chave de identificação | Consulta entre conjuntos |
| Eficiência de armazenamento | Alta (índices compartilhados) | Baixa (índices duplicados em campos comuns) |
| Consistência dos dados | Naturalmente consistente (dentro do mesmo conjunto) | Requer manutenção (atualizações entre conjuntos) |
| Tamanho do índice | Pequeno (um índice para campos comuns) | Grande (criar um índice separado para cada coleção) |
| Desempenho da consulta | Um pouco mais lento (requer filtragem por função) | Rápido (conjunto menor) |
| Escalabilidade | Ruim (expansão de conjuntos) | Boa (escalonamento independente) |
| Casos de uso | Diferenças mínimas entre campos, consultas com junções frequentes | Diferenças significativas entre campos, consultas principalmente independentes |
Casos de uso: Funções de usuário (Cliente/Administrador/Moderador compartilham e-mail e senha, cada uma com seus próprios campos específicos); formas de pagamento (Cartão de crédito/PayPal/Transferência bancária compartilham valor e status, cada uma com campos específicos do canal); tipos de notificação (E-mail/SMS/Notificação push compartilham assunto e conteúdo, cada um com configurações específicas do canal).
// === Basics User Model ===
const UserSchema = new mongoose.Schema({
email: String,
username: String,
passwordHash: String
}, { discriminatorKey: 'role' });
const User = mongoose.model('User', UserSchema);
// === Customer Discriminator ===
const Customer = User.discriminator('Customer', new mongoose.Schema({
loyaltyPoints: { type: Number, default: 0 },
preferredCategories: [String]
}));
// === Admin Discriminator ===
const Admin = User.discriminator('Admin', new mongoose.Schema({
permissions: [String],
lastLoginAt: Date
}));
// === Create Different Roles ===
const customer = await Customer.create({
email: 'alice@example.com',
username: 'alice',
passwordHash: '...',
loyaltyPoints: 100,
preferredCategories: ['Electronics']
});
const admin = await Admin.create({
email: 'admin@example.com',
username: 'admin',
passwordHash: '...',
permissions: ['manage_products']
});
// === When querying, based on role Category ===
const customers = await Customer.find();
const admins = await Admin.find();
// All data is in the same collection(users),Through discriminatorKey Category
Caso de uso: Coleção única com vários esquemas (campos diferentes para funções diferentes).
4. Otimização de desempenho da função lean()
O que é lean()? lean() é um método de otimização de desempenho no Mongoose — ele ignora o processo de hidratação do documento e retorna diretamente um objeto JavaScript simples. Enquanto as consultas comuns retornam um documento Mongoose (com métodos como save() e validate(), além do rastreamento de alterações), lean() retorna um objeto simples (contendo apenas dados, sem nenhum método).
Princípios por trás das diferenças de desempenho no lean():
graph LR
subgraph "General Query (without lean)"
T1[Product.find] --> D1[mongoose Document<br/>with save/validate etc.<br/>~150ms / 100 docs]
end
subgraph "lean Search"
Q2[MongoDB Original BSON] --> H2[JSON.parse Direct Conversion]
H2 --> D2[plain object<br/>Raw Data<br/>~30ms / 100 docs]
end
style D1 fill:#f8d7da
style D2 fill:#d4edda
Dados de comparação de desempenho (100 documentos, categoria Eletrônicos):
| Operação | Sem Lean | Com Lean | Multiplicador de desempenho |
|---|---|---|---|
| Tempo de consulta | ~150 ms | ~30 ms | ↑5x |
| Uso de memória | ~5 MB | ~1 MB | ↓5x |
| JSON.stringify | ~8 ms | ~2 ms | ↑4x |
| Suporta save() | ✅ | ❌ | — |
| Suporta preenchimento | ✅ | ✅ (chamadas encadeadas) | — |
| Compatível com Controle de Alterações | ✅ | ❌ | — |
Diretrizes de uso: Use lean() para APIs somente leitura (listas, detalhes); não use lean() se precisar chamar save() ou modificar o rastreamento; adicione lean() se não forem necessárias outras modificações após populate.
// === General Inquiry:Back mongoose Document ===
const products = await Product.find();
// Each product is a Mongoose Document (with save() and other methods)
// === lean():Return to Pure JS Object ===
const products = await Product.find().lean();
// Each product is a plain object, Performance up 3-5x
// === Comparison Test ===
console.time('without lean');
const a = await Product.find({ category: 'Electronics' }).limit(100);
console.timeEnd('without lean'); // ~150ms
console.time('with lean');
const b = await Product.find({ category: 'Electronics' }).lean().limit(100);
console.timeEnd('with lean'); // ~30ms
5. Pipeline de agregação Model.aggregate()
Canais de agregação no Mongoose: Model.aggregate() aciona diretamente o mecanismo de agregação do MongoDB para realizar agrupamentos, junções e cálculos na camada do banco de dados — ao contrário do populate, que lida com essas operações na camada de aplicação; os dados nos canais de agregação não precisam ser transferidos para o lado do Node.js para processamento, resultando em melhor desempenho.
Guia para escolher entre aggregate e populate:
| Cenário | Solução recomendada | Motivo |
|---|---|---|
| Consultar pedido + nome de usuário | preencher | Relação simples, código limpo |
| Calcular o preço médio para cada categoria | agregar | Os cálculos devem ser agrupados |
| Juntar + Agrupar + Ordenar | agregar + $lookup | Tudo em uma única etapa |
| Junções aninhadas em vários níveis | agregação + várias chamadas $lookup | Evitar N+1 |
| Retornar resultados multidimensionais | aggregate + $facet | Retornar várias visualizações de uma só vez |
Processo de execução do pipeline de agregação:
graph LR
Input[1,000,000 Documents] -->|"$match"| Filter[After filtering: 500,000]
Filter -->|"$group"| Group[Group by category<br/>5 groups]
Group -->|"$sort"| Sorted[Sort by count]
Sorted -->|"$limit"| Output[Top 5]
style Input fill:#f8d7da
style Output fill:#d4edda
// === mongoose Using Aggregation in ===
const stats = await Product.aggregate([
{ $match: { isActive: true } },
{ $group: { _id: '$category', count: { $sum: 1 }, avgPrice: { $avg: '$price' } } },
{ $sort: { count: -1 } }
]);
// === aggregate + populate(mongoose 6+)===
const results = await Order.aggregate([
{ $match: { status: 'paid' } },
{
$lookup: {
from: 'users',
localField: 'userId',
foreignField: '_id',
as: 'customer'
}
},
{ $unwind: '$customer' }
]);
6. Declaração de índice de esquema
Métodos de declaração de índices no Mongoose: O Mongoose oferece suporte à criação declarativa de índices dentro das definições de esquema — índices no nível do campo (index: true), índices compostos (Schema.index()) e índices especiais (índices de texto, índices TTL e índices parciais). A vantagem dos índices declarativos é que eles são definidos junto com o esquema, tornando-os fáceis de entender à primeira vista; eles são criados automaticamente na inicialização (autoIndex=true).
Tipos de índices e casos de uso:
| Tipo de índice | Método de declaração | Casos de uso | Parâmetros especiais |
|---|---|---|---|
| Índice de campo único | { sku: { index: true } } |
Consultas de igualdade, ordenação | único |
| Índice Composto | Schema.index({a:1, b:-1}) |
Consulta com múltiplas condições | Regra ESR |
| Índice de texto | { title: { text: true } } |
Pesquisa de texto completo | Pesos |
| Índice TTL | Schema.index({at:1}, {expireAfterSeconds:86400}) |
Expiração automática | Tempo de expiração |
| Índice parcial | partialFilterExpression |
Índice de condições | Filtrar condições |
| Índice geográfico | { loc: { type: '2dsphere' } } |
Pesquisa geográfica | — |
Regra ESR (Equality-Sort-Range): A ordem dos campos em um índice composto deve seguir a seguinte sequência: condições de igualdade → condições de classificação → condições de intervalo. Por exemplo, { category: 1, price: -1 } suporta find({category:'E'}) + sort({price:-1}), mas não suporta a classificação apenas por preço.
const ProductSchema = new mongoose.Schema({
sku: { type: String, index: true, unique: true },
title: { type: String, text: true }, // Text Index
price: { type: Number, index: true },
category: { type: String, index: true }
});
// === Composite Index ===
ProductSchema.index({ category: 1, price: -1 });
// === Selected Indexes ===
ProductSchema.index(
{ category: 1 },
{ partialFilterExpression: { isActive: true } }
);
// === TTL Index ===
ProductSchema.index(
{ createdAt: 1 },
{ expireAfterSeconds: 30 * 24 * 60 * 60 }
);
7. Otimização de desempenho do Mongoose 7.x
Metodologia de otimização de desempenho do Mongoose: A otimização de desempenho não se resume simplesmente a “adicionar uma chamada lean() e dar o trabalho por encerrado”; envolve, na verdade, um ajuste sistemático desde a camada de conexão → camada de consulta → camada de aplicação → camada de implantação. Os princípios fundamentais são: reduzir o volume de transferência de dados, minimizar o número de consultas, reduzir a sobrecarga de serialização e aproveitar os recursos nativos do banco de dados.
Matriz de Estratégias de Otimização:
| Camada de otimização | Estratégia | Eficácia | Invasividade |
|---|---|---|---|
| Camada de conexão | Ajuste do maxPoolSize | Reduzir o tempo de espera pela conexão | Baixo (Configuração) |
| Camada de consulta | Seleção de projeção (select()) | Reduz a transferência de dados em mais de 90% | Baixa |
| Nível da consulta | Índice + hint() | Evitar varredura completa da tabela | Médio |
| Camada de consulta | lean() | Reduz a sobrecarga do Hydrate em 5 vezes | Baixa |
| Camada de aplicação | Paralelismo com Promise.all | Reduz o tempo de espera em série | Baixo |
| Camada de aplicação | Operações em lote com bulkWrite | Reduz as idas e voltas na rede em 10 vezes ↑ | Média |
| Camada de Aplicação | Transmissão de cursor | Como evitar o estouro de memória | Médio |
| Nível de implantação | autoIndex=false | Inicialização mais rápida | Baixo |
| Camada de implantação | Separação de leitura e gravação | Reduz a carga no nó primário | Alta |
Práticas de otimização de Charlie: A API da lista de produtos da TechCorp foi otimizada, passando de 3 segundos para 50 milissegundos — ① Adicionou-se índices compostos para evitar o COLLSCAN; ② Utilizou-se select() para consultar apenas 5 campos; ③ Utilizou-se lean() para pular hydrate; ④ Utilizou Promise.all para executar find e count em paralelo; ⑤ Definia um limite de 100 linhas.
// === Optimization 1:Disable autoIndex(Production)===
mongoose.connect(uri, { autoIndex: false });
// Manually create indexes at startup:await Product.syncIndexes();
// === Optimization 2:Bulk Operations ===
await Product.bulkWrite([
{ updateOne: { filter: { sku: 'A' }, update: { $inc: { stock: -1 } } } },
{ updateOne: { filter: { sku: 'B' }, update: { $inc: { stock: -1 } } } }
]);
// === Optimization 3:Projection Reduces Data Transmission ===
const products = await Product.find()
.select('sku title price') // Search only 3 field
.lean();
// === Optimization 4:Usage cursor Streaming Processing of Big Data ===
const cursor = Product.find().cursor();
for await (const doc of cursor) {
// Process each document
}
// === Optimization 5:Bulk Insert ===
await Product.insertMany(docs, { ordered: false });
8. Treinamento prático abrangente
// === Optimized List API ===
app.get('/api/products', async (req, res) => {
const { page = 1, limit = 20, category, search } = req.query;
// 1. Build a Query
const query = { isActive: true };
if (category) query.category = category;
if (search) query.$text = { $search: search };
// 2. Parallel Queries(find + count)
const [products, total] = await Promise.all([
Product.find(query)
.select('sku title price thumbnail rating') // Projection
.sort({ createdAt: -1 })
.limit(+limit)
.skip((page - 1) * limit)
.lean(), // Performance Optimization
Product.countDocuments(query)
]);
res.json({
success: true,
data: products,
meta: { page: +page, limit: +limit, total, pages: Math.ceil(total / limit) }
});
});
▶ Exemplo 1: Preenchimento de relações multiníveis + otimização enxuta do desempenho
// === Scene:ShopHub Order Details API(3 Layer Association)===
const mongoose = require('mongoose');
// Schemas
const AddressSchema = new mongoose.Schema({ city: String, country: String, zipCode: String });
const UserSchema = new mongoose.Schema({
email: String, username: String,
addresses: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Address' }]
});
const ProductSchema = new mongoose.Schema({ sku: String, title: String, price: Number, thumbnail: String });
const OrderSchema = new mongoose.Schema({
orderNumber: String,
userId: { type: mongoose.Schema.Types.ObjectId, ref: 'User' },
items: [{ productId: { type: mongoose.Schema.Types.ObjectId, ref: 'Product' }, qty: Number, price: Number }],
status: String
}, { timestamps: true });
const Address = mongoose.model('Address', AddressSchema);
const User = mongoose.model('User', UserSchema);
const Product = mongoose.model('Product', ProductSchema);
const Order = mongoose.model('Order', OrderSchema);
// 3-level populate + lean
const order = await Order.findById('647f1f77bcf86cd799439001')
.populate({ path: 'userId', select: 'username email',
populate: { path: 'addresses', select: 'city country' }
})
.populate({ path: 'items.productId', select: 'sku title price' })
.lean();
console.log({
orderNumber: order.orderNumber,
customer: order.userId.username,
city: order.userId.addresses[0]?.city,
items: order.items.map(i => `${i.productId.title} x${i.qty}`)
});
// Output:{ orderNumber: 'ORD-001', customer: 'alice', city: 'San Francisco',
// items: ['Smartphone X x2', 'Laptop Pro x1'] }
Resultado: 3 camadas de
populate(Pedido → Usuário → Endereço + Pedido → Produto) + otimização de desempenholean().
▶ Exemplo: E-commerce Dashboard com populate, lean, e aggregate (Difficulty ⭐⭐)
// Scene: ShopHub admin dashboard showing orders with customer and product details
const mongoose = require('mongoose');
// Schema with refs
const OrderSchema = new mongoose.Schema({
orderNumber: String,
customerId: { type: mongoose.Schema.Types.ObjectId, ref: 'User' },
items: [{
productId: { type: mongoose.Schema.Types.ObjectId, ref: 'Product' },
qty: Number,
price: Number
}],
status: { type: String, enum: ['pending', 'paid', 'shipped', 'delivered'] },
total: Number,
createdAt: { type: Date, default: Date.now }
});
const Order = mongoose.model('Order', OrderSchema);
// Method 1: populate (good for small datasets)
async function getOrdersWithPopulate() {
return await Order.find({ status: 'paid' })
.populate('customerId', 'username email')
.populate('items.productId', 'sku title')
.sort({ createdAt: -1 })
.limit(20)
.lean(); // Combine with lean for read-only
}
// Method 2: aggregate with $lookup (better for large dataset + filtering)
async function getOrdersWithAggregate() {
return await Order.aggregate([
{ $match: { status: 'paid' } },
{ $sort: { createdAt: -1 } },
{ $limit: 20 },
// Join customer
{
$lookup: {
from: 'users',
localField: 'customerId',
foreignField: '_id',
as: 'customer'
}
},
{ $unwind: '$customer' },
// Join products
{
$lookup: {
from: 'products',
localField: 'items.productId',
foreignField: '_id',
as: 'productDetails'
}
},
// Final shape
{
$project: {
orderNumber: 1,
total: 1,
status: 1,
customer: { username: '$customer.username', email: '$customer.email' },
itemCount: { $size: '$items' }
}
}
]);
}
// Usage
const orders = await getOrdersWithPopulate();
console.log(`${orders.length} orders loaded with customer info`);
Saída:
TEXT 📖 Somente leitura20 orders loaded with customer info // Each order includes customer.username, customer.email, and items with product details
▶ Exemplo 2: Uma aplicação prática abrangente de Populate + Discriminator + Lean
// === 1. populate Multi-level relationships ===
// Order + User + Products(3 Nested Layers)
const order = await Order.findById(orderId)
.populate({
path: 'userId',
select: 'username email avatar',
populate: { path: 'addresses', select: 'city country' } // Address under the user
})
.populate({
path: 'items.productId',
select: 'sku title price thumbnail'
})
.lean(); // Performance Optimization
console.log('Order:', {
orderNumber: order.orderNumber,
customer: {
username: order.userId.username,
address: order.userId.addresses[0]?.city
},
items: order.items.map(i => ({
product: i.productId.title,
qty: i.qty,
price: i.price
}))
});
// === 2. discriminator Single Set, Multiple Schema ===
// User Base Class
const UserSchema = new mongoose.Schema({
email: { type: String, required: true, unique: true },
username: String,
passwordHash: String,
createdAt: { type: Date, default: Date.now }
}, { discriminatorKey: 'role' });
const User = mongoose.model('User', UserSchema);
// Customer Discriminator(Inheritance User + Extended Fields)
const Customer = User.discriminator('Customer', new mongoose.Schema({
loyaltyPoints: { type: Number, default: 0 },
preferredCategories: [String],
totalSpent: mongoose.Schema.Types.Decimal128
}));
// Admin Discriminator
const Admin = User.discriminator('Admin', new mongoose.Schema({
permissions: [String],
lastLoginAt: Date
}));
// Create Different Roles(All exist users Gathering,Through role Field Delimiter)
const customer = await Customer.create({
email: 'alice@example.com',
username: 'alice',
passwordHash: '...',
loyaltyPoints: 100,
preferredCategories: ['Electronics']
});
const admin = await Admin.create({
email: 'admin@example.com',
username: 'admin',
passwordHash: '...',
permissions: ['manage_products', 'manage_users']
});
// When querying, based on role Automatic Filtering
const customers = await Customer.find({ loyaltyPoints: { $gt: 50 } });
// Actual Query:{ role: 'Customer', loyaltyPoints: { $gt: 50 } }
// === 3. lean() Performance Optimization Comparison ===
console.time('without lean');
const a = await Product.find({ category: 'Electronics' }).limit(100);
console.timeEnd('without lean'); // ~150ms
console.time('with lean');
const b = await Product.find({ category: 'Electronics' }).lean().limit(100);
console.timeEnd('with lean'); // ~30ms(5x Performance Improvements)
// === 4. Model.aggregate() Database-Level Aggregation ===
const stats = await Product.aggregate([
{ $match: { isActive: true } },
{
$facet: {
totalCount: [{ $count: 'count' }],
byCategory: [
{ $group: { _id: '$category', count: { $sum: 1 }, avgPrice: { $avg: '$price' } } },
{ $sort: { count: -1 } }
],
topRated: [
{ $sort: { rating: -1 } },
{ $limit: 5 },
{ $project: { sku: 1, title: 1, rating: 1 } }
]
}
}
]);
// Output:
// {
// totalCount: [{ count: 1250 }],
// byCategory: [
// { _id: 'Electronics', count: 450, avgPrice: 599 },
// { _id: 'Books', count: 380, avgPrice: 29 },
// ...
// ],
// topRated: [
// { sku: 'PHONE-001', title: 'Smartphone X', rating: 4.9 },
// ...
// ]
// }
Resultado:
populateimplementa associações em vários níveis;discriminatorsuporta múltiplas funções dentro de um único conjunto;lean()proporciona um aumento de 5 vezes no desempenho; eaggregaterealiza uma única agregação na camada do banco de dados para retornar resultados multidimensionais.
❓ Perguntas Frequentes
P: Quando se deve usar
populateem vez de$lookup? R: Usepopulatepara conjuntos de dados pequenos (flexibilidade na camada de aplicação) e$lookuppara conjuntos de dados grandes (eficiência na camada de banco de dados).
P: Existe alguma diferença entre um discriminador e uma coleção? R: Os discriminadores compartilham a mesma coleção (distinguida pelo campo “role”), enquanto as coleções independentes são fisicamente isoladas.
P: Posso chamar
save()depois delean()? R: Não.lean()retorna um objeto simples que não possui métodos do Mongoose. Se você precisar chamarsave(), consulte o documento novamente ou use os métodos do documento.
📖 Resumo
- preencher: Consultas com junções (risco N+1)
- discriminador: conjunto único, múltiplos esquemas
- lean(): Otimização de desempenho ↑3–5x
- Model.aggregate(): Agregação no nível do banco de dados
- Declaração de índice do esquema
- Dicas de otimização de desempenho para o Mongoose 7.x
📝 Exercícios
- Problema básico (⭐): Implemente uma consulta de junção
populate(Pedido + Usuário + Produto). - Problema básico (⭐): Use
lean()para otimizar a API da lista de produtos e comparar as diferenças de desempenho. - Problema avançado (⭐⭐): Use um discriminador para implementar as três funções: Usuário, Cliente e Administrador.
- Exercício avançado (⭐⭐): Use
bulkWritepara atualizar o estoque de produtos em massa (lidar com situações de falta de estoque). - Desafio (3 estrelas): Conclua as APIs avançadas do Mongoose (populate + lean + aggregate + discriminator).