MongoDB: mongoose高级模式:性能与灵活性

最后更新:2026-08-26

mongoose 高级模式——掌握 populate、discriminator、lean、aggregate 等高级特性。

1. 你将学到


100%
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 底层原理

100%
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

JAVASCRIPT
// === 基本 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 底层机制

100%
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 共享标题+内容,各有渠道配置)。

JAVASCRIPT
// === 基础 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() 性能差异原理

100%
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()。

JAVASCRIPT
// === 普通查询:返回 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 一次返回多视图

聚合管道执行流程

100%
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
JAVASCRIPT
// === 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 排序。

JAVASCRIPT
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 条。

JAVASCRIPT
// === 优化 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. 综合实战

JAVASCRIPT
// === 优化后的列表 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 性能优化

JAVASCRIPT
// === 场景: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 综合实战

JAVASCRIPT
// === 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 扩展:虚拟属性计算价格和折扣、实例方法管理库存、静态方法查询热销商品。

JAVASCRIPT
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 })。

❓ 常见问题

Q 什么时候用 populate vs $lookup?
A 小数据集用 populate(应用层灵活),大数据集用 $lookup(数据库层高效)。
Q discriminator 和 collection 不同?
A discriminator 共享同一集合(用 role 字段区分),独立 collection 是物理隔离。
Q lean() 后能 save() 吗?
A 不能。lean() 返回 plain object,无 mongoose 方法。如需 save(),再查询一次或用文档方法。

📖 小节


📝 作业

  1. 基础题(⭐):实现 populate 关联查询(Order + User + Product)。
  2. 基础题(⭐):用 lean() 优化商品列表 API,对比性能差异。
  3. 进阶题(⭐⭐):用 discriminator 实现 User / Customer / Admin 三种角色。
  4. 进阶题(⭐⭐):用 bulkWrite 批量更新商品库存(处理库存不足)。
  5. 挑战题(⭐⭐⭐):完整 mongoose 高级 API(populate + lean + aggregate + discriminator)。
Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏