node.js-猫鼬填充与对象嵌套

使用猫鼬填充和直接对象包含之间是否有性能差异(查询的处理时间)? 什么时候应该使用?

猫鼬人口的例子:

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,
});
Marius Cotofana asked 2020-07-01T13:55:15Z
1个解决方案
109 votes

关于猫鼬种群的第一件事是,它不是魔术,而是一种便捷的方法,它使您无需亲自完成操作即可检索相关信息。

该概念主要用于以下情况:您决定需要将数据放置在单独的集合中,而不是将数据嵌入,并且主要考虑因素通常应在文档大小上,或者相关信息会经常更新,从而使 难以维护嵌入式数据。

“非魔术”部分实际上是在幕后发生的事情是,当您“引用”另一个源时,填充函数会对“相关”集合进行附加查询/查询,以“合并”父对象的结果。 您检索到的对象。 您可以自己执行此操作,但是此处提供的方法是为了简化任务。 明显的“性能”考虑因素是,没有一个往返数据库(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对如何处理不支持“ joins” 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限制。 您始终可以考虑使用“存储桶”的混合方法,但是不能违反主文档的一般硬性限制。 常见的情况是“发布”和“评论”,其中“评论”活动预计会很大。

  • 相关数据需要定期更新。 或本质上是您进行“规范化”的情况,因为该数据在许多父级之间“共享”并且“相关”数据被频繁更改,以至于在发生“子级”项的每个“父级”中更新嵌入项都是不切实际的 。 更简单的情况是仅引用“子项”并进行一次更改。

  • 读写之间有明显的区别。 如果您在阅读“父母”时可能不会总是要求“相关”信息,或者在写给孩子时不一定要始终更改“父母”,则可能有充分的理由分离模型 作为参考。 此外,如果普遍希望一次更新许多“子文档”,而这些“子文档”实际上是对另一个集合的引用,那么当数据位于单独的位置时,实现通常会更高效 采集。

因此,实际上,关于数据建模的MongoDB文档中的任一立场都对“利弊”进行了更广泛的讨论,其中涵盖了各种用例以及由populate方法支持的使用嵌入或引用模型的方法。

希望可以使用“点”,但是通常的建议是考虑应用程序的数据使用模式并选择最佳的模式。 选择“应该”来嵌入“应该”是您选择MongoDB的原因,但实际上这将是您的应用程序“使用数据”的方式,从而决定哪种方法适合您数据建模的哪一部分(因为并非如此) “全有还是全无”)最好。

  1. 请注意,由于这是最初编写的,所以MongoDB引入了$lookup20运算符,该运算符的确在服务器上的集合之间执行“联接”。 为了此处的一般讨论目的,在大多数情况下,“更好”是$lookup产生的“多个查询”开销,而通常是“多个查询”,任何2947321063652459559520操作都产生了“大量开销”。

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

Neil Lunn answered 2020-07-01T13:57:13Z
translate from https://stackoverflow.com:/questions/24096546/mongoose-populate-vs-object-nesting