【问题标题】:Mongoose populate vs object nesting猫鼬填充与对象嵌套
【发布时间】:2014-07-28 14:13:12
【问题描述】:

使用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,
});

Mongoose 对象嵌套示例:

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

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

【问题讨论】:

标签: node.js mongodb mongoose


【解决方案1】:

首先要了解猫鼬种群,它不是魔术,只是一种方便的方法,让您无需自己动手就可以检索相关信息。

该概念主要用于您决定需要将数据放在单独的集合中而不是嵌入该数据的地方,并且您的主要考虑因素通常应该是文档大小或相关信息需要经常更新的地方这将使维护嵌入式数据变得笨拙。

“不神奇”的部分本质上是在幕后发生的事情是,当您“引用”另一个源时,填充函数会对该“相关”集合进行额外的查询/查询,以便“合并”这些结果您检索到的父对象。您可以自己执行此操作,但该方法是为了方便简化任务。明显的“性能”考虑是没有单次往返数据库(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 }

“参考”模型或使用填充(在后台)可以完成的“最佳”是这样的:

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") });

两者的优缺点在很大程度上取决于您的应用程序的使用模式。但一目了然:

嵌入

  • 包含嵌入数据的总文档大小通常不会超过 16MB 的存储空间(BSON 限制),否则(作为准则)具有包含 500 个或更多条目的数组。

  • 嵌入的数据通常不需要频繁更改。因此,您可以忍受来自非规范化的“重复”,而不会导致需要使用跨许多父文档的相同信息更新这些“重复”来调用更改。

  • 相关数据经常与父级关联使用。这意味着,如果您的“读/写”案例几乎总是需要“读/写”父和子,那么嵌入数据以进行原子操作是有意义的。

参考

  • 相关数据总是会超过 16MB BSON 限制。您始终可以考虑“分桶”的混合方法,但不能违反主文档的一般硬限制。常见的情况是“post”和“cmets”,其中“comment”活动预计会非常大。

  • 相关数据需要定期更新。或者本质上是您“规范化”的情况,因为该数据在许多父母之间“共享”,并且“相关”数据更改得足够频繁,以至于在出现“孩子”项目的每个“父母”中更新嵌入项目是不切实际的.更简单的情况是只引用“孩子”并进行一次更改。

  • 读取和写入有明确的分离。如果您在阅读“父母”时可能并不总是需要“相关”信息,或者在写给孩子时不需要总是改变“父母”,那么可能有充分的理由将模型分开如参考。此外,如果普遍希望一次更新许多“子文档”,其中这些“子文档”实际上是对另一个集合的引用,那么当数据位于单独的收藏。

因此,在Data Modelling 上的 MongoDB 文档中,实际上对任一位置的“优点/缺点”进行了更广泛的讨论,其中涵盖了各种用例以及使用嵌入或引用模型的方法,由填充方法。

希望“点”是有用的,但一般建议是考虑您的应用程序的数据使用模式并选择最好的。拥有嵌入“应该”的“选项”是您选择 MongoDB 的原因,但实际上它将是您的应用程序如何“使用数据”来决定哪种方法适合您的数据建模的哪一部分(因为它不是“全有或全无”)最好的。

  1. 请注意,由于这是最初编写的,MongoDB 引入了$lookup 运算符,它确实在服务器上的集合之间执行“连接”。出于这里一般讨论的目的,在大多数情况下,populate() 产生的“多次查询”开销和一般的“多次查询”开销“更好”,但仍然存在 “显着开销” 发生在任何$lookup 操作中。

核心设计原则是“嵌入”意味着“已经存在”,而不是“从其他地方获取”。本质上是“在你的口袋里”和“在书架上”之间的区别,在 I/O 术语中通常更像“在市中心图书馆的书架上”,尤其是对于基于网络的请求更远.

【讨论】:

  • 关于最后的注释,我发现this mongoose issue 的基准似乎比 $lookup 更快地归因于填充速度...我了解多个查询的开销,但对性能感到困惑。 ..
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-02-09
  • 2016-08-17
  • 2021-11-07
  • 2018-10-15
  • 1970-01-01
  • 2021-12-25
  • 2014-09-16
相关资源
最近更新 更多