MongoDB: mongoose高级模式:性能与灵活性
最后更新:2026-08-26
mongoose 高级模式——掌握 populate、discriminator、lean、aggregate 等高级特性。
1. 你将学到
- populate 关联查询(与 $lookup 对比)
- discriminator 鉴别器
- lean() 性能优化
- Model.aggregate() 聚合管道
- Schema 索引声明
- mongoose 7.x 性能优化技巧
graph TB
A[mongoose 高级特性] --> B[populate<br/>应用层 JOIN]
A --> C[discriminator<br/>单集合多 Schema]
A --> D[lean<br/>性能优化]
A --> E[aggregate<br/>数据库层聚合]
A --> F[Schema 索引<br/>声明式]
B --> B1[多次查询<br/>N+1 风险]
C --> C1[role 字段<br/>区分类型]
D --> D1[跳过 hydrate<br/>↑5x 性能]
E --> E1[$facet/$lookup<br/>一次返回多维度]
style D fill:#d4edda
2. populate 关联查询
什么是 populate? populate 是 mongoose 实现的"应用层 JOIN"——当 Schema 中有 ObjectId 类型的 ref 字段时,populate 会自动发起额外查询,将引用的 ObjectId 替换为完整文档。它本质上是两次查询:先查主文档获取 ObjectId,再查关联文档获取完整数据。
populate 底层原理:
sequenceDiagram
participant App as Node.js 应用
participant Mongo as MongoDB
App->>Mongo: 第1次查询: Order.find()
Mongo-->>App: 返回 orders (userId = ObjectId("..."))
App->>Mongo: 第2次查询: User.find({_id: {$in: [ObjectId1, ObjectId2, ...]}})
Mongo-->>App: 返回 users
App->>App: 合并: order.userId → user 对象
populate vs $lookup 对比:
| 维度 | populate | $lookup |
|---|---|---|
| 执行层 | 应用层(2+ 次查询) | 数据库层(1 次聚合) |
| 查询次数 | N+1 风险 | 1 次 |
| 灵活性 | 中(仅支持 ObjectId 引用) | 高(任意条件关联) |
| 性能 | 适合小数据集 | 大数据集推荐 |
| 代码简洁度 | 高(一行 .populate()) | 低(聚合管道语法) |
| 返回类型 | mongoose Document | plain object |
N+1 问题:查询 100 个订单,populate 每个 order.userId 会触发 1(查订单)+ 100(查用户)= 101 次查询。mongoose 会自动优化为 $in 批量查询(1+1=2 次),但嵌套 populate 仍可能产生额外查询。
使用场景:单文档关联(查一个订单的用户)用 populate;批量关联 + 复杂条件用 $lookup;纯展示用 lean + populate。
(1) 基本 populate
// === 基本 populate ===
const user = await User.findById(userId).populate('addresses');
// SELECT u.*, a.* FROM users u LEFT JOIN addresses a ON u._id = a.userId
// === 嵌套 populate ===
const order = await Order.findById(orderId)
.populate('userId')
.populate({
path: 'items.productId',
select: 'sku title price'
});
// === 条件 populate ===
const orders = await Order.find()
.populate({
path: 'userId',
match: { isActive: true },
select: 'username avatar'
});
| populate vs $lookup | populate | $lookup |
|---|---|---|
| 执行层 | 应用层 | 数据库层 |
| 查询次数 | N+1 | 1 |
| 灵活性 | 中 | 高 |
| 性能 | 适合小数据集 | 大数据集推荐 |
3. discriminator 鉴别器
什么是 discriminator? discriminator 是 mongoose 的"单集合继承"机制——多个 Model 共享同一个 MongoDB 集合,通过鉴别键(discriminatorKey)区分文档类型。类似于面向对象编程中的继承:基类定义公共字段,子类扩展特有字段,所有实例存在同一张表。
discriminator 底层机制:
graph TB
subgraph "users 集合(单一集合)"
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 层"
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 -->|查询: role='Customer'| D1
Customer -->|查询: role='Customer'| D2
Admin -->|查询: role='Admin'| D3
style User fill:#fff3cd
style Customer fill:#d4edda
style Admin fill:#cce5ff
discriminator vs 独立集合:
| 维度 | discriminator(单集合) | 独立集合 |
|---|---|---|
| 查询方式 | 按鉴别键自动过滤 | 跨集合查询 |
| 存储效率 | 高(共享索引) | 低(重复公共字段索引) |
| 数据一致性 | 天然一致(同一集合) | 需维护(跨集合更新) |
| 索引大小 | 小(公共字段一份索引) | 大(每集合各建索引) |
| 查询性能 | 略慢(需过滤 role) | 快(集合更小) |
| 扩展性 | 差(集合膨胀) | 好(独立扩展) |
| 适用场景 | 字段差异小,频繁联合查询 | 字段差异大,独立查询为主 |
使用场景:用户角色(Customer/Admin/Moderator 共享 email+password,各有特有字段)、支付方式(CreditCard/PayPal/BankTransfer 共享金额+状态,各有渠道特有字段)、通知类型(Email/SMS/Push 共享标题+内容,各有渠道配置)。
// === 基础 User Model ===
const UserSchema = new mongoose.Schema({
email: String,
username: String,
passwordHash: String
}, { discriminatorKey: 'role' });
const User = mongoose.model('User', UserSchema);
// === Customer 鉴别器 ===
const Customer = User.discriminator('Customer', new mongoose.Schema({
loyaltyPoints: { type: Number, default: 0 },
preferredCategories: [String]
}));
// === Admin 鉴别器 ===
const Admin = User.discriminator('Admin', new mongoose.Schema({
permissions: [String],
lastLoginAt: Date
}));
// === 创建不同角色 ===
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']
});
// === 查询时根据 role 区分 ===
const customers = await Customer.find();
const admins = await Admin.find();
// 所有数据在同一集合(users),通过 discriminatorKey 区分
应用场景:单集合多 Schema(不同角色不同字段)。
4. lean() 性能优化
什么是 lean()? lean() 是 mongoose 的性能优化方法——跳过文档水合(hydration)过程,直接返回纯 JavaScript 对象。普通查询返回 mongoose Document(带 save()、validate() 等方法和变更追踪),lean() 返回 plain object(只有数据,没有方法)。
lean() 性能差异原理:
graph LR
subgraph "普通查询(无 lean)"
Q1[MongoDB 原始 BSON] --> H1[hydrate: BSON → Document]
H1 --> T1[变更追踪注册]
T1 --> D1[mongoose Document<br/>带 save/validate 等<br/>~150ms / 100条]
end
subgraph "lean 查询"
Q2[MongoDB 原始 BSON] --> H2[JSON.parse 直接转换]
H2 --> D2[plain object<br/>纯数据<br/>~30ms / 100条]
end
style D1 fill:#f8d7da
style D2 fill:#d4edda
性能对比数据(100 条文档,Electronics 类别):
| 操作 | 无 lean | 有 lean | 性能倍数 |
|---|---|---|---|
| 查询时间 | ~150ms | ~30ms | ↑5x |
| 内存占用 | ~5MB | ~1MB | ↓5x |
| JSON.stringify | ~8ms | ~2ms | ↑4x |
| 支持 save() | ✅ | ❌ | — |
| 支持 populate | ✅ | ✅(链式调用) | — |
| 支持修改追踪 | ✅ | ❌ | — |
使用原则:只读 API(列表、详情)用 lean();需要 save() 或修改追踪的不用 lean();populate 后不需要再修改的加 lean()。
// === 普通查询:返回 mongoose Document ===
const products = await Product.find();
// 每个 product 是 Mongoose Document(带 save() 等方法)
// === lean():返回纯 JS 对象 ===
const products = await Product.find().lean();
// 每个 product 是 plain object,性能 ↑3-5x
// === 对比测试 ===
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. Model.aggregate() 聚合管道
mongoose 中的聚合管道:Model.aggregate() 直接调用 MongoDB 的聚合引擎,在数据库层完成分组、关联、计算——与 populate 的应用层处理不同,聚合管道的数据不需要传输到 Node.js 端再处理,性能更优。
aggregate vs populate 选择指南:
| 场景 | 推荐方案 | 原因 |
|---|---|---|
| 查订单+用户名 | populate | 简单关联,代码简洁 |
| 统计各品类平均价格 | aggregate | 需要分组计算 |
| 关联+分组+排序 | aggregate + $lookup | 一步完成 |
| 多层嵌套关联 | aggregate + 多 $lookup | 避免 N+1 |
| 返回多维度结果 | aggregate + $facet | 一次返回多视图 |
聚合管道执行流程:
graph LR
Input[100万文档] -->|"$match"| Filter[筛选后50万]
Filter -->|"$group"| Group[按category分组<br/>5组]
Group -->|"$sort"| Sorted[按count排序]
Sorted -->|"$limit"| Output[Top 5]
style Input fill:#f8d7da
style Output fill:#d4edda
// === mongoose 中使用聚合 ===
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. Schema 索引声明
mongoose 索引声明方式:mongoose 支持在 Schema 定义中声明式创建索引——字段级索引(index: true)、复合索引(Schema.index())、特殊索引(文本索引 text、TTL 索引、部分索引)。声明式索引的优势是:索引与 Schema 定义在一起,一目了然;启动时自动创建(autoIndex=true)。
索引类型与适用场景:
| 索引类型 | 声明方式 | 适用场景 | 特殊参数 |
|---|---|---|---|
| 单字段索引 | { sku: { index: true } } |
等值查询、排序 | unique |
| 复合索引 | Schema.index({a:1, b:-1}) |
多条件查询 | ESR 规则 |
| 文本索引 | { title: { text: true } } |
全文搜索 | 权重 weights |
| TTL 索引 | Schema.index({at:1}, {expireAfterSeconds:86400}) |
自动过期 | 过期时间 |
| 部分索引 | partialFilterExpression |
条件索引 | 过滤条件 |
| 地理索引 | { loc: { type: '2dsphere' } } |
地理查询 | — |
ESR 规则(Equality-Sort-Range):复合索引字段顺序应遵循:等值条件 → 排序条件 → 范围条件。如 { category: 1, price: -1 } 支持 find({category:'E'}) + sort({price:-1}),但不支持单独按 price 排序。
const ProductSchema = new mongoose.Schema({
sku: { type: String, index: true, unique: true },
title: { type: String, text: true }, // 文本索引
price: { type: Number, index: true },
category: { type: String, index: true }
});
// === 复合索引 ===
ProductSchema.index({ category: 1, price: -1 });
// === 部分索引 ===
ProductSchema.index(
{ category: 1 },
{ partialFilterExpression: { isActive: true } }
);
// === TTL 索引 ===
ProductSchema.index(
{ createdAt: 1 },
{ expireAfterSeconds: 30 * 24 * 60 * 60 }
);
7. mongoose 7.x 性能优化
mongoose 性能优化方法论:性能优化不是"加个 lean() 就完了",而是从连接层→查询层→应用层→部署层的系统化调优。核心原则是:减少数据传输量、减少查询次数、减少序列化开销、利用数据库原生能力。
优化策略矩阵:
| 优化层 | 策略 | 效果 | 侵入性 |
|---|---|---|---|
| 连接层 | maxPoolSize 调优 | 减少连接等待 | 低(配置) |
| 查询层 | 投影 select() | 减少传输 90%+ | 低 |
| 查询层 | 索引 + hint() | 避免全表扫描 | 中 |
| 查询层 | lean() | 减少 hydrate 开销 ↑5x | 低 |
| 应用层 | Promise.all 并行 | 减少串行等待 | 低 |
| 应用层 | bulkWrite 批量操作 | 减少网络往返 ↑10x | 中 |
| 应用层 | cursor 流式处理 | 避免内存溢出 | 中 |
| 部署层 | autoIndex=false | 加速启动 | 低 |
| 部署层 | 读写分离 | 分担主节点压力 | 高 |
Charlie 的优化实践:TechCorp 的商品列表 API 从 3s 优化到 50ms——① 加复合索引避免 COLLSCAN;② select() 只查 5 个字段;③ lean() 跳过 hydrate;④ Promise.all 并行 find+count;⑤ limit 限制最大 100 条。
// === 优化 1:禁用 autoIndex(生产)===
mongoose.connect(uri, { autoIndex: false });
// 启动时手动创建索引:await Product.syncIndexes();
// === 优化 2:批量操作 ===
await Product.bulkWrite([
{ updateOne: { filter: { sku: 'A' }, update: { $inc: { stock: -1 } } } },
{ updateOne: { filter: { sku: 'B' }, update: { $inc: { stock: -1 } } } }
]);
// === 优化 3:投影减少数据传输 ===
const products = await Product.find()
.select('sku title price') // 只查询 3 个字段
.lean();
// === 优化 4:使用 cursor 流式处理大数据 ===
const cursor = Product.find().cursor();
for await (const doc of cursor) {
// 处理每个文档
}
// === 优化 5:批量插入 ===
await Product.insertMany(docs, { ordered: false });
8. 综合实战
// === 优化后的列表 API ===
app.get('/api/products', async (req, res) => {
const { page = 1, limit = 20, category, search } = req.query;
// 1. 构建查询
const query = { isActive: true };
if (category) query.category = category;
if (search) query.$text = { $search: search };
// 2. 并行查询(find + count)
const [products, total] = await Promise.all([
Product.find(query)
.select('sku title price thumbnail rating') // 投影
.sort({ createdAt: -1 })
.limit(+limit)
.skip((page - 1) * limit)
.lean(), // 性能优化
Product.countDocuments(query)
]);
res.json({
success: true,
data: products,
meta: { page: +page, limit: +limit, total, pages: Math.ceil(total / limit) }
});
});
▶ 示例 1:populate 多层关联 + lean 性能优化
// === 场景:ShopHub 订单详情 API(3 层关联)===
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 层 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}`)
});
// 输出:{ orderNumber: 'ORD-001', customer: 'alice', city: 'San Francisco',
// items: ['Smartphone X x2', 'Laptop Pro x1'] }
输出:3 层 populate(Order→User→Address + Order→Product)+ lean() 性能优化。
▶ 示例 2:populate + discriminator + lean 综合实战
// === 1. populate 多层关联 ===
// 订单 + 用户 + 商品(3 层嵌套)
const order = await Order.findById(orderId)
.populate({
path: 'userId',
select: 'username email avatar',
populate: { path: 'addresses', select: 'city country' } // 用户下的地址
})
.populate({
path: 'items.productId',
select: 'sku title price thumbnail'
})
.lean(); // 性能优化
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 单集合多 Schema ===
// User 基类
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 鉴别器(继承 User + 扩展字段)
const Customer = User.discriminator('Customer', new mongoose.Schema({
loyaltyPoints: { type: Number, default: 0 },
preferredCategories: [String],
totalSpent: mongoose.Schema.Types.Decimal128
}));
// Admin 鉴别器
const Admin = User.discriminator('Admin', new mongoose.Schema({
permissions: [String],
lastLoginAt: Date
}));
// 创建不同角色(都存在 users 集合,通过 role 字段区分)
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']
});
// 查询时根据 role 自动过滤
const customers = await Customer.find({ loyaltyPoints: { $gt: 50 } });
// 实际查询:{ role: 'Customer', loyaltyPoints: { $gt: 50 } }
// === 3. lean() 性能优化对比 ===
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 性能提升)
// === 4. Model.aggregate() 数据库层聚合 ===
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 } }
]
}
}
]);
// 输出:
// {
// 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 },
// ...
// ]
// }
输出:populate 实现多层关联,discriminator 单集合多角色,lean() 性能提升 5x,aggregate 数据库层聚合一次返回多维度结果。
▶ 示例 3:Schema 虚拟属性 + 实例方法 + 静态方法综合实战
mongoose Schema 有三种扩展机制——虚拟属性(不存储但可计算)、实例方法(文档级操作)、静态方法(模型级操作)。它们让业务逻辑从 Controller 下沉到 Model,实现更清晰的代码分层。本示例为电商系统实现完整的 Schema 扩展:虚拟属性计算价格和折扣、实例方法管理库存、静态方法查询热销商品。
const mongoose = require('mongoose');
// === 1. 商品 Schema + 三种扩展 ===
const productSchema = new mongoose.Schema({
sku: { type: String, required: true, unique: true },
title: { type: String, required: true },
category: { type: String, required: true },
price: { type: Number, required: true, min: 0 },
discountPercent: { type: Number, default: 0, min: 0, max: 100 },
stock: { type: Number, default: 0, min: 0 },
ratings: [{ userId: String, score: Number }],
createdAt: { type: Date, default: Date.now }
}, {
toJSON: { virtuals: true },
toObject: { virtuals: true },
id: false
});
// === 2. 虚拟属性(不存入数据库,动态计算)===
productSchema.virtual('finalPrice').get(function() {
return Math.round(this.price * (1 - this.discountPercent / 100) * 100) / 100;
});
productSchema.virtual('isOnSale').get(function() {
return this.discountPercent > 0;
});
productSchema.virtual('avgRating').get(function() {
if (!this.ratings || this.ratings.length === 0) return null;
const sum = this.ratings.reduce((acc, r) => acc + r.score, 0);
return Math.round(sum / this.ratings.length * 10) / 10;
});
productSchema.virtual('stockStatus').get(function() {
if (this.stock === 0) return 'out_of_stock';
if (this.stock < 10) return 'low_stock';
return 'in_stock';
});
// === 3. 实例方法(文档级操作,this 指向文档实例)===
productSchema.methods.applyDiscount = function(percent) {
if (percent < 0 || percent > 80) throw new Error('折扣范围 0-80%');
this.discountPercent = percent;
return this.save();
};
productSchema.methods.reduceStock = async function(qty) {
if (this.stock < qty) throw new Error(`库存不足:当前 ${this.stock},需要 ${qty}`);
this.stock -= qty;
return this.save();
};
productSchema.methods.addRating = function(userId, score) {
const existing = this.ratings.find(r => r.userId === userId);
if (existing) {
existing.score = score;
} else {
this.ratings.push({ userId, score });
}
return this.save();
};
// === 4. 静态方法(模型级操作,this 指向模型)===
productSchema.statics.findOnSale = function() {
return this.find({ discountPercent: { $gt: 0 } }).lean();
};
productSchema.statics.findLowStock = function(threshold = 10) {
return this.find({ stock: { $lt: threshold, $gt: 0 } }).lean();
};
productSchema.statics.getTopRated = function(limit = 10) {
return this.aggregate([
{ $addFields: { avgScore: { $avg: '$ratings.score' } } },
{ $match: { avgScore: { $gte: 4 } } },
{ $sort: { avgScore: -1 } },
{ $limit: limit }
]);
};
productSchema.statics.searchByKeyword = function(keyword, page = 1, limit = 20) {
const regex = new RegExp(keyword, 'i');
return this.find({
$or: [{ title: regex }, { sku: regex }, { category: regex }]
})
.skip((page - 1) * limit)
.limit(limit)
.lean();
};
const Product = mongoose.model('Product', productSchema);
// === 5. 使用示例 ===
const phone = new Product({
sku: 'PHONE-001', title: 'Smartphone X', category: 'Electronics',
price: 599, discountPercent: 15, stock: 50
});
await phone.save();
console.log(phone.finalPrice); // 509.15
console.log(phone.isOnSale); // true
console.log(phone.stockStatus); // 'in_stock'
await phone.addRating('user1', 5);
await phone.addRating('user2', 4);
console.log(phone.avgRating); // 4.5
await phone.reduceStock(5);
console.log(phone.stock); // 45
const onSale = await Product.findOnSale();
const topRated = await Product.getTopRated(5);
输出:虚拟属性 finalPrice/avgRating/stockStatus 动态计算不占存储;实例方法 applyDiscount/reduceStock/addRating 封装业务操作;静态方法 findOnSale/getTopRated/searchByKeyword 提供模型级查询。
三种扩展机制的选择原则:1. 虚拟属性——用于纯计算字段(价格×折扣、平均评分),不存入数据库,每次访问时计算;2. 实例方法——用于单个文档的业务操作(减少库存、评分),涉及 this.save() 持久化;3. 静态方法——用于跨文档的查询/统计(热销商品、搜索),相当于自定义 Model.find();4. 注意 toJSON/toObject 必须设置 virtuals: true,否则 JSON 序列化时虚拟属性不会出现;5. 虚拟属性不能用于查询条件——Product.find({ finalPrice: 500 }) 无效,必须用 Product.find({ price: 500, discountPercent: 0 })。
❓ 常见问题
📖 小节
- populate:关联查询(N+1 风险)
- discriminator:单集合多 Schema
- lean():性能优化 ↑3-5x
- Model.aggregate():数据库层聚合
- Schema 索引声明
- mongoose 7.x 性能优化技巧
📝 作业
- 基础题(⭐):实现 populate 关联查询(Order + User + Product)。
- 基础题(⭐):用 lean() 优化商品列表 API,对比性能差异。
- 进阶题(⭐⭐):用 discriminator 实现 User / Customer / Admin 三种角色。
- 进阶题(⭐⭐):用 bulkWrite 批量更新商品库存(处理库存不足)。
- 挑战题(⭐⭐⭐):完整 mongoose 高级 API(populate + lean + aggregate + discriminator)。