一尘不染

mongoosejs填充与对象嵌套

node.js

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

猫鼬人口的例子:

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

阅读 296

收藏
2020-07-07

共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对如何处理不支持“ 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引入了$lookup运算符,该运算符的确在服务器上的集合之间执行“联接”。出于此处一般性讨论的目的,在大多数情况下,“更好”是由“多重查询”引起的开销,而在一般情况下是“多重查询”
populate(),因此任何操作仍然存在
“大量开销”$lookup

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

2020-07-07