使用Mongoose population和直接对象包含之间是否存在性能差异(查询的处理时间)?什么时候使用?

mongoose 种群示例:

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

推荐答案

关于mongoose 种群,首先要了解的是,它不是魔法,只是一种方便的方法,可以让你在不必自己动手的情况下检索相关信息.

基本上,当您决定需要将数据放在一个单独的集合中,而不是嵌入该数据时,可以使用该概念,并且您的主要考虑因素通常应该是文档大小,或者相关信息需要频繁更新,这会使维护嵌入的数据变得非常困难.

"不神奇"的部分是,本质上,当您"引用"另一个源时,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 }

"参考"模型或使用填充(在引擎盖下)可以做到的"最佳"是:

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

因此,为了"连接"这些数据,显然"至少"有两个查询和操作.

嵌入概念本质上是MongoDB对如何处理不支持"joins"1的回答.因此,与其将数据拆分为规范化的集合,不如try 将"相关"数据直接嵌入到使用它的文档中.这里的优点是,有一个用于检索"相关"信息的"读取"操作,还有一个用于更新"父"和"子"条目的单点"写入"操作,虽然通常不可能在不处理客户机上的"列表"或以其他方式接受"多个"写入操作的情况下同时向"多个"子级写入,最好是在"批处理"处理中.

然后,数据看起来是这样的(与上面的示例相比):

{ 
    "_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个或更多条目的array.

  • 嵌入的数据通常不需要频繁更改.因此,您可以接受来自go 规范化的"重复",而不需要在许多父文档中使用相同的信息来更新这些"重复",只是为了调用更改.

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

引用

  • 相关数据总是会超过16MB BSON限制.你总是可以考虑一种混合的"ButkTebug"方法,但是主文档的一般硬限制不能被 destruct .常见的情况是"发布"和" comments ",其中" comments "活动预计会非常大.

  • 相关数据需要定期更新.或者基本上是"标准化"的情况,因为该数据在多个父级之间"共享",并且"相关"数据的更改足够频繁,以至于在出现该"子"项的每个"父级"中更新嵌入的项是不切实际的.更简单的情况是只引用"child"并进行一次更改.

  • 读写之间有明确的分离.如果您在阅读"家长"时可能并不总是需要"相关"信息,或者在给子元素写信时不需要总是更改"家长",那么就有很好的理由将参考的模型分开.此外,如果普遍希望同时更新多个"子文档",其中这些"子文档"实际上是对另一个集合的引用,那么当数据位于单独的集合中时,实现通常更高效.

因此,对于MongoDB文档Data Modelling中的任何一种立场,实际上都有更广泛的"优缺点"讨论,其中涵盖了各种用例,以及使用填充方法支持的嵌入或引用模型的方法.

希望"点"是有用的,但一般的建议是考虑应用程序的数据使用模式,并 Select 什么是最好的. Select MongoDB的原因是有"选项"嵌入"应该",但实际上是应用程序"使用数据"的方式决定了哪种方法最适合数据建模的哪一部分(因为它不是"全部或全部").

  1. 请注意,由于这是最初编写的,MongoDB引入了$lookup操作符,它确实在服务器上的集合之间执行"连接".在这里进行一般性讨论时,whist"更好"在大多数情况下,populate()和"多个查询"产生的"多个查询"开销一般来说,任何$lookup个操作都会产生"significant overhead".

核心设计原则是"嵌入式"意味着"已经存在",而不是"从其他地方获取".本质上是"在你的口袋里"和"在架子上"之间的区别,在I/O术语中,通常更像是"on the shelf in the library downtown",尤其是对于基于网络的请求来说,距离更远.

Node.js相关问答推荐

DocuSign:调用createEntaine时,RequestJWTApplicationToken返回401 AUTHORIZATION_INVALID_TOKEN

无法从MongoDB文档中保存的对象数组中获取对象的属性

如何修复PostgreSQL和NodeJS/NestJS应用程序之间的日期时间和时区问题?

如何在 Firestore 函数上使用类型模型来获取字段值

express app.post的多个参数在Node.js中的定义是什么

yarn 安装失败,因为 node-gyp 正在寻找过时的 node 版本标头

使用正则表达式查找文档,但输入是数组

在 Atlas 触发器(Node JS)中正确初始化 Firebase 管理 SDK

更新文档数组中的文档 Mongoose

mongoose.model() 方法返回未定义

tsc:当我上传 React+next js 和 node 项目时,在 heroku 找不到

当我使用 uuid 代码意外崩溃,然后工作正常?

如何申请在NextJS上下载文件的许可?

我们如何或可以通过 npm 和 Meteor 使用 node 模块?

如何从 Redis 保存和检索会话

Passport 登录和持久会话

JavaScript 异步编程:promise 与生成器

Node.js 17.0.1 Gatsby 错误-数字信封 routine ::不支持 ... ERR_OSSL_EVP_UNSUPPORTED

如何让 Mocha 加载定义全局挂钩或实用程序的 helper.js 文件?

找不到在 docker compose 环境中运行的 node js 应用程序的模块