Mongoose populate与对象嵌套

59

在使用Mongoose population和直接包含对象之间,是否存在性能差异(查询的处理时间)?应该在什么情况下使用每个选项?

Mongoose population示例:

var personSchema = Schema({
  _id     : Number,
  name    : String,
  stories : [{ type: Schema.Types.ObjectId, ref: 'Story' }]
});

var storySchema = Schema({
  _creator : { type: Number, ref: 'Person' },
  title    : String,
});

蒙古斯对象嵌套示例:

var personSchema = Schema({
  _id     : Number,
  name    : String,
  stories : [storySchema]
});

var storySchema = Schema({
  _creator : personSchema,
  title    : String,
});

3
这不是一个简单的问题。你可能希望阅读MongoDB的数据建模,并决定什么最适合你的需要:http://docs.mongodb.org/manual/core/data-modeling-introduction - Ben
1个回答

137
首先需要理解的是mongoose population并非魔法,只是一种方便的方法,使您能够检索相关信息而无需自行完成所有操作。
本概念主要用于决定是否需要将数据放置在单独的集合中而不是嵌入该数据,在此情况下,您应主要考虑文档大小或相关信息经常更新会导致维护嵌入式数据困难。
“不是魔法”的部分基本上是指,在“引用”另一个来源时,populate函数会向该“相关”集合进行附加查询/查询,以便“合并”您检索到的父对象的结果。您可以自己执行此操作,但该方法仅为简化任务而提供。明显的“性能”问题是,并非仅有单个回传访问数据库(MongoDB实例)即可检索所有信息,通常情况下需要多次回传。
例如,有两个集合:
{ 
    "_id": ObjectId("5392fea00ff066b7d533a765"),
    "customerName": "Bill",
    "items": [
        ObjectId("5392fee10ff066b7d533a766"),
        ObjectId("5392fefe0ff066b7d533a767")
    ]
}

和项目:

{ "_id": ObjectId("5392fee10ff066b7d533a766"), "prod": "ABC", "qty": 1 }
{ "_id": ObjectId("5392fefe0ff066b7d533a767"), "prod": "XYZ", "qty": 2 }

使用“引用”模型或使用populate(在底层)可以实现的“最佳”效果是:

var order = db.orders.findOne({ "_id": ObjectId("5392fea00ff066b7d533a765") });
order.items = db.items.find({ "_id": { "$in": order.items } ).toArray();

所以,显然需要“至少”两个查询和操作才能“连接”这些数据。
嵌入概念本质上是MongoDB对于如何处理不支持“连接”的答案1。因此,与其将数据拆分为规范化的集合,您可以尝试直接在使用它的文档中嵌入“相关”的数据。这里的优点是,有一个单独的“读取”操作来检索“相关”信息,并且还有一个单一的“写入”操作点来更新“父”和“子”条目,尽管通常无法一次写入“多个”子项而不在客户端上处理“列表”或以其他方式接受“多个”写操作,最好是批量处理。
与上面的示例相比,数据看起来像这样:
{ 
    "_id": ObjectId("5392fea00ff066b7d533a765"),
    "customerName": "Bill",
    "items": [
        { "_id": ObjectId("5392fee10ff066b7d533a766"), "prod": "ABC", "qty": 1 },
        { "_id": ObjectId("5392fefe0ff066b7d533a767"), "prod": "XYZ", "qty": 2 }
    ]
}

因此,实际上获取数据只是以下事项:
db.orders.findOne({ "_id": ObjectId("5392fea00ff066b7d533a765") });

The pros and cons of embedding or referencing largely depend on the usage pattern of your application. Here are some general considerations:

Embedding

  • The total document size with embedded data should not exceed 16MB of storage (the BSON limit) or contain arrays with 500 or more entries.

  • Data that is embedded generally does not require frequent changes. Therefore, duplication resulting from de-normalization may be acceptable to avoid updating the same information across many parent documents.

  • Related data is frequently used in association with the parent. If your "read/write" cases require frequent access to both parent and child data, it makes sense to embed the data for atomic operations.

Referencing

  • 相关数据总是会超过16MB的BSON限制。您可以考虑“分桶”的混合方法,但主文档的一般硬限制不能被突破。常见情况是“帖子”和“评论”,其中预计“评论”活动非常大。

  • 相关数据需要定期更新。或者本质上是因为那些数据在许多父级之间“共享”,并且“相关”数据经常更改,因此在每个“出现子项的“父项”中更新嵌入式项将是不切实际的情况下,“规范化”。更容易的情况是只引用“子项”并进行一次更改。

  • 读取和写入有明显的分离。在这种情况下,当您不总是需要在读取“父项”时要求“相关”信息,或者在写入子项时不需要始终更改“父项”时,可能有很好的理由将模型分开作为引用。此外,如果通常希望同时更新许多“子文档”,其中那些“子文档”实际上是对另一个集合的引用,则在数据位于单独的集合中时,实现往往更有效率。

所以,实际上在 MongoDB 的 数据建模 文档中,对于嵌入和引用模型的 "优缺点" 进行了更广泛的讨论,涵盖了各种用例和方法来处理使用嵌入或引用模型,正如 populate 方法所支持的那样。希望这些 "点号" 有用,但通常建议考虑应用程序的数据使用模式并选择最佳方法。拥有嵌入选项 "应该" 是您选择 MongoDB 的原因,但实际上是您的应用程序如何 "使用数据" 来决定哪种方法最适合您数据建模的哪个部分(因为它不是 "全有还是全无")。
请注意,由于MongoDB最初引入了$lookup运算符,该运算符确实在服务器上在集合之间执行“连接”。对于这里的一般讨论目的而言,在大多数情况下比populate()和多个查询中产生的“多个查询”开销更好,但使用任何$lookup操作仍会产生"显着开销"。 核心设计原则是“嵌入式”意味着“已经存在”,而不是“从其他地方获取”。本质上,“在你的口袋里”和“在架子上”的区别,在I/O术语中通常更像“在市区图书馆的书架上”,尤其是对于基于网络的请求而言距离更远。

5
关于最后的说明,我发现了这个猫鼬问题,其中一个基准测试显示populate的速度比$lookup快... 我明白多次查询的开销,但对性能感到困惑... - Miquel

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接