Mongoose性能对比:Schema关联ID与Populate哪种更优?
背景说明
我们有以下两个MongoDB Schema:
原始Menu Schema
{ restaurant: { type: String, unique: true, required: true }, color: { type: String, required: true }, }
原始Plate Schema
{ name: { type: String, unique: true, required: true }, }
需求是从Menu获取所有关联的Plate,以下是两种关联方案的性能分析,重点考虑Plate数量较多的场景。
方案一:在Menu中存储Plate ID数组
修改后的Menu Schema(修正原代码中ref笔误,应为'Plate'):
{ restaurant: { type: String, unique: true, required: true }, color: { type: String, required: true }, plates: [ { type: Schema.Types.ObjectId, ref: 'Plate' } ] }
查询方式:
Menu.findById(id).populate('plates')
性能分析
- 适合Plate数量较少的场景:一次查询Menu后,通过
populate就能批量获取关联Plate,写法简洁。 - Plate数量较多时的弊端:
- 文档体积膨胀:
plates数组会持续增大,容易触发MongoDB单个文档16MB的限制,后续新增Plate时还需频繁修改Menu文档,降低写性能。 - 查询开销高:
populate本质是两次查询——先查Menu拿到所有Plate ID,再用$in查询Plate集合。当ID数量极多时,$in条件会变得庞大,查询性能显著下降。
- 文档体积膨胀:
方案二:在Plate中存储Menu ID
修改后的Plate Schema(修正原代码中ref笔误,应为'Menu'):
{ name: { type: String, unique: true, required: true }, menu: { type: Schema.Types.ObjectId, ref: 'Menu' } }
查询方式:
Plate.find({ menu: menu_id })
性能分析
- 更适合Plate数量较多的场景:
- 文档结构稳定:每个Plate仅存储一个Menu ID,文档体积小,无单个文档超限风险;新增Plate时仅需写入单个文档,写性能稳定。
- 查询效率高:给
menu字段添加索引后,查询会从全表扫描变为索引扫描,即使Plate数量达到百万级,也能快速返回结果。
- 对比方案一,避免了
populate的二次查询开销,也不存在Menu文档膨胀的问题,是Plate数量多时的更优选择。
更优的关联优化方案
如果需要进一步提升性能,可做以下优化:
- 强制给Plate的
menu字段加索引:这是方案二的核心优化,能将查询性能提升几个数量级,索引创建语句:PlateSchema.index({ menu: 1 }) - 复杂查询用聚合
$lookup:若需同时获取Menu详情和关联Plate,可使用聚合查询替代populate,性能与方案二加索引后相当,但写法更灵活:Menu.aggregate([ { $match: { _id: menu_id } }, { $lookup: { from: 'plates', localField: '_id', foreignField: 'menu', as: 'plates' }} ]) - 超大规模数据分片:当Plate数量过亿时,可按
menu字段对Plate集合进行分片,分散数据存储和查询压力,进一步提升性能。
内容的提问来源于stack exchange,提问作者Ignacio Passerini
相关产品推荐
相关产品推荐

