MongoDB一对多-子模型上的ID数组或ID引用?

MongoDB一对多-子模型上的ID数组或ID引用?,mongodb,mongoose,database-design,Mongodb,Mongoose,Database Design,具体来说,我想到的是一个聊天应用程序,其中一个用户有许多聊天s,每个聊天都有许多消息s。如何表达聊天--有许多-->消息关系 起初,我认为我应该将消息ID引用列表存储在聊天上,例如聊天文档可能看起来像 { _id: ObjectId('507f191e810c19729de860d5'), title: 'Jack V, Kyle R, Sam P', messages: [ObjectId('507f191e810c19729de860ea'), ...], createdAt

具体来说,我想到的是一个聊天应用程序,其中一个
用户
有许多
聊天
s,每个
聊天
都有许多
消息
s。如何表达
聊天--有许多-->消息关系

起初,我认为我应该将
消息
ID引用列表存储在
聊天
上,例如
聊天
文档可能看起来像

{
  _id: ObjectId('507f191e810c19729de860d5'),
  title: 'Jack V, Kyle R, Sam P',
  messages: [ObjectId('507f191e810c19729de860ea'), ...],
  createdAt: 1546284204867
}
这样,每次发送消息时,我都需要
$push
到数组,如果消息被删除,则需要
$pull
。使代码更加复杂和模糊,但仍然可行

然后我从中了解到,这种方法只有在阵列较小且增长有限的情况下才成立。为了避免可变的、不断增长的数组,最好将
Message
保留一个
chatId
ref返回到其父
Chat

{
  _id: ObjectId('507f191e810c19729de860ea'),
  body: 'Hey Kyle! Mind if I ask ya a favor?',
  chatId: ObjectId('507f191e810c19729de860d5'),
  createdAt: 1546284204869
}
最后,如果我想获取消息与猫鼬聊天

const chatId = '507f191e810c19729de860d5'

// 1. through IDs array
Chat.findById(chatId).populate('messages').execPopulate()

// or

// 2. through ID ref
Message.find({ chatId })
据我了解,

  • 第一个更有效,b/c它需要一组ID,并快速定位
    消息
    文档,然后才读取它们
  • 第二个效率较低,b/c必须读取所有
    消息
    文档(可能有数百万个),并比较每个文档上的
    chatId
    属性
这与MongoDB中的读操作要比写操作昂贵得多这一事实是一致的。我说的对吗?如果是这样,为什么大多数资源都推荐方法2?我在(见黄色方框)、MongoDB开发人员的50个技巧和窍门以及MongoDB文档中看到了它


对于一对多关系,最好在父模型上维护一个ID数组,还是在每个子模型上都有一个ID ref?

更新写入实际上相当昂贵。插入新文档速度很快,但更新需要一些时间,因为您需要执行读和写操作。如果
O(r)
是“读取”的时间复杂度,
O(w)
是“写入”的时间复杂度,则更新是
O(r+w)
。如果你在你查询的字段上建立了一个索引,那么读取实际上也是非常高效的,所以通常你不需要担心这个问题。要遵循的一般建议是将更新量保持在最低限度,而读取和插入都可以,尽管只要索引良好,这些操作都不是问题

除此之外,我不建议在
聊天
文档中对
消息
进行非规范化。文档大小限制为16MB,因此如果聊天变得特别大,MongoDB将无法处理它。即使它从未超过该限制,您也无法优化消息检索——任何时候您想要加载聊天,您都需要一次抓取所有消息,但在最现实的场景中,您只需要检索最后几十条消息,并根据需要加载更多!此外,将您的邮件作为单独的文档保存将允许您执行其他有用的任务,例如仅搜索和显示特定人员发送的邮件、跳过某些时间点、清除所有早于给定日期的文档、创建TTL索引以自动删除旧邮件等


因此,就潜在的功能、性能、文档大小限制,甚至只是易于管理而言,使用单独的
消息
文档及其相应的
聊天
的父级引用是首选方法。

回答得非常好!您认为在
Message
中的
chatId
上设置一个索引应该会加快对一组特别大的文档(例如数百万)的读取速度吗?当然!我在一个生产数据库中工作过,在一个集合中有数百万个文档,只要有适当的索引,读取就会很快(即几乎是即时的)。问题在于,当您没有建立索引时,会导致完整的集合扫描,这通常会占用资源,甚至超时。然而,这是所有数据库的一个问题,也是大型数据集的一个基本限制。因此,只要确保这些索引到位:)P.S.这是如何被认为是基于意见的?这个问题涉及一个非常具体的场景,有两种可能的解决方案。答案几乎不是基于观点,而是基于事实和标准。这无异于询问,我是否应该使用
location.replace
vs
location.href
。这个网站上的每个问题在某种程度上都是基于观点的。那么,这一点是如何特别突出的呢?