【问题标题】:Mongo DB relations between documents in different collections不同集合中文档之间的MongoDB关系
【发布时间】:2015-08-08 04:46:44
【问题描述】:

我还没有准备好放手,这就是我重新思考问题并编辑 Q 的原因(原文如下)。


我在周末项目中使用 mongoDB,它需要数据库中的一些关系,这就是痛苦的全部原因:

我有三个收藏:

Users
Lists
Texts

用户可以拥有文本和列表 - 列表“包含”文本。文本可以在多个列表中。

我决定使用单独的集合(而不是嵌入),因为子文档并不总是出现在其父文档的上下文中(例如,所有文本,而不是在列表中)。

所以需要做的是使用这些列表引用属于某些列表的文本。可以有无限的列表和文本,但相比之下列表会更少。

与我最初想到的不同,我还可以将引用放在每个文本文档中,而不是列表文档中的所有文本 ID。它实际上会有所作为,因为我可以通过一个查询来查找列表中的每个 sn-p。甚至可以索引该参考。

var TextSchema = new Schema({
      _id: Number,
      name: String,
      inListID: { type : Array , "default" : [] },
      [...]

文本出现在 MANY 列表中的情况也很少见,因此数组不会真正爆炸。不过,问题仍然存在,是否有可能使用 mongoDB 进行扩展或实际上是一种更好的实现方式?限制文本可以包含的列表数量(可能)是否有帮助?有少数关系的秘诀吗?

获得对已完成此操作以及如何实施的项目的引用(很少:很多关系)甚至会很棒。我不敢相信一旦需要一些关系,每个人都会回避 mongo DB。



原始问题

我将把它分解为我目前看到的两个问题: 1) 假设一个列表由 5 个文本组成。如何引用列表中包含的文本?只需打开一个数组并将文本的 _ids 存储在其中吗?似乎这些阵列可能会增长到月球并返回,从而减慢应用程序的速度?另一方面,文本需要在没有列表的情况下可用,因此嵌入并不是真正的选择。如果我想获取包含 100 个文本的列表的所有文本怎么办......听起来像两个查询和一个包含 100 个字段的数组:-/。那么这种引用方式是否正确?

var ListSchema = new Schema({
  _id: Number,
  name: String,
  textids: { type : Array , "default" : [] },
  [...]

问题 2)我看到这种方法是在删除文本时清理引用。它的引用仍将在包含文本的每个列表中,我不想遍历所有列表以清除那些死引用。或者我会?有没有聪明的方法来解决这个问题?只是让文本包含参考(它们在哪个列表中)只会解决问题,所以这不是一个选项。

我想我不是第一个遇到此类问题的人,但我也无法找到关于如何“正确”解决问题的明确答案。

我还对有关此类引用(多对多?),尤其是可扩展性/性能的最佳实践的一般想法感兴趣。

【问题讨论】:

标签: node.js mongodb express mongoose


【解决方案1】:

我不确定这个问题是否仍然存在,但我有类似的经历。
首先我想说的是官方 mongo documentation:

在以下情况下使用嵌入式数据模型:您有一对一或一对多的模型。
用于模型多对多使用与文档引用的关系。

我认为是答案)但是这个答案提供了很多问题,因为:

  1. 如前所述,mongo 根本不提供事务。
  2. 而且您没有外键约束。
  3. 即使您在文档之间有引用 (DBRefs),您仍将面临如何取消引用这些文档的惊人问题。

即使您在周末项目中工作,每个项目 - 都是巨大的责任。这可能意味着您应该编写许多代码来提供系统的简单行为(例如,您可以看到如何在 mongo here 中实现事务。

我不知道外键约束是如何完成的,而且我在 mongo 文档中没有看到这个方向的东西,这就是为什么我认为它具有惊人的挑战(和项目风险)。

最后,mongo 引用 - 它不是 mysql join,并且您不会从父集合中接收所有数据以及来自子集合的数据(如表中的所有字段和 mysql 中连接表中的所有字段),您将收到只需 REFERENCE 到另一个集合中的另一个文档,您将需要对这个引用做一些事情(取消引用)。 它可以很容易地通过回调在节点中到达,但前提是您只需要一个列表中的一个文本,但如果您需要一个列表中的所有文本 - 这很糟糕,但如果您需要多个列表中的所有文本 - 它是成为噩梦......

也许这不是我最好的体验......但我认为你应该考虑一下......

【讨论】:

    【解决方案2】:

    写一个答案,因为我想解释我将如何从这里开始。

    考虑到这里的答案和我自己对该主题的研究,将这些引用(不是真正的关系)存储在一个数组中实际上可能很好,尽量保持相对较小:我的字段很可能少于 1000 个案例。

    尤其是因为我可以摆脱一个查询(虽然我第一次做不到),到目前为止甚至不需要使用$in,我相信这种方法会扩展。毕竟它“只是一个周末项目”,所以如果没有,我最终会重写 - 那很好。

    使用这样的文本模式:

    var textSchema = new Schema({
      _id: {type: Number, required: true, index: { unique: true }},
      ...
      inList: { type : [Number] , "default" : [], index: true }
    });
    

    我可以通过这个查询简单地获取列表中的所有文本,其中inList 是一个索引数组,其中包含列表中文本的_ids

    Text.find({inList: listID}, function(err, text) {
      ...      
    });
    

    我仍然需要处理外键约束并编写我自己的“清理”函数来处理删除列表时的引用 - 删除列表中每个文本中的引用。 幸运的是,这种情况很少发生,所以我可以偶尔浏览一下每个文本。

    另一方面,如果删除了文本,我不必关心删除列表文档中的引用,因为我只将引用存储在关系的一侧(在文本文档中)。在我看来非常重要的一点!

    @mnemosyn:感谢链接并指出这确实不是一个大连接,或者换句话说:只是一个非常简单的关系。还有一些关于这些复杂操作需要多长时间的数字(ofc。硬件依赖)是一个很大的帮助。
    PS:Grüße aus Bielefeld。

    我在自己的研究was this vid 中发现最有帮助的内容,Alvin Richards 也在其中谈到了多对多关系。 17. 这就是我产生将关系单向化的想法,以便为自己节省一些清理死引用的工作。

    感谢大家的帮助 ?

    【讨论】:

      【解决方案3】:

      关系通常不是一个大问题,尽管涉及关系的某些操作可能是。这在很大程度上取决于您要解决的问题,并且很大程度上取决于结果集的基数和键的选择性。

      我写过a simple testbed,它会根据典型的长尾分布生成数据。事实证明,MongoDB 在关系方面通常比人们想象的要好。

      毕竟和关系型数据库只有三个区别:

      • 外键限制:您必须自己管理这些限制,因此存在死链接的风险
      • 事务隔离:由于没有多文档事务,即使代码正确(从不尝试创建死链接的意义上说),也有可能创建无效的外键约束,但只是在运行时中断.此外,很难检查死链接,因为您可能正在观察竞争条件
      • 联接:MongoDB 不支持联接,尽管带有 $in 的手动子查询可以很好地扩展到 $in 子句中的数千个项目,当然前提是参考值已编入索引

      如果您需要执行大型连接,即如果您的查询是真正的关系查询并且您需要相应地连接 大量 数据,那么 MongoDB 可能不适合。但是,关系数据库中所需的许多连接并不是真正关系的,它们是必需的,因为您必须将对象拆分为多个表,例如因为它包含一个列表。

      “真正的”关系查询的一个示例可能是“找到所有购买产品的客户,这些客户在 6 月份的营业额排名靠前的客户的评价超过 4 星”。除非您有一个非常专业的架构,基本上是为支持此查询而构建的,否则您很可能需要找到所有订单,按客户 ID 对它们进行分组,获取前 n 个结果,然后使用这些使用$in 查询评级并使用另一个$in 查找实际客户。不过,如果您可以将自己限制在顶部,比如 6 月份的 10k 客户,这是三个往返和一些快速的$in 查询。

      只要 RAM 中的索引支持您的查询并且网络没有完全拥塞,这在典型的云硬件上可能会在 10-30 毫秒的范围内。在这个例子中,如果数据太稀疏,事情就会变得一团糟,即前 10k 用户几乎没有写过超过 4 星的评论,这将迫使你编写足够聪明的程序逻辑来不断迭代第一步,这既复杂又缓慢,但如果这是一个如此重要的场景,那么无论如何可能会有更适合的数据结构。

      【讨论】:

        【解决方案4】:

        在 MongoDB 中使用数组通常是不可取的,专家也不建议这样做。

        这是我想到的一个解决方案:

        Users 的每个文档始终是唯一的。 Users 中的单个文档可以有ListsTexts。因此,ListsTexts 有一个 USER ID 字段,即 _idUsers

        ListsUsers 中始终有一个所有者,因此它们按原样存储。

        Texts 的所有者可以是 UsersList,因此您还应该在其中保留一个 LIST ID 字段,即 _id of Lists

        现在请注意,Texts 不能同时具有 USER ID 和 LIST ID,因此您必须保持一个条件,即两者都应该只有一个,另一个应该是 null,这样我们就可以很容易地知道谁是Texts 的主要所有者。

        【讨论】:

        • 您好,谢谢您的回答。正如您所指出的,列表和文本归唯一用户所有,因此两者都已经有一个“所有者”字段。我认为行不通的是让文本由用户或列表拥有 - 它必须同时属于两者!文本由用户拥有(例如,按用户查询所有文本)并且可以在不同的列表中(每个文本文档中的 List_ids 数组)。而这个确切的数组是我看到瓶颈的地方..即使在索引时等等。我摆脱了一个查询..我可能很快就会写一篇后续文章。感谢您的帮助!
        【解决方案5】:

        将 MongoDB 与引用一起使用是解决性能问题的途径。什么不使用的完美例子。这是m:n 类型的关系,其中mn 可以扩展到数百万。 MongoDB 在我们有1:n(few)1:n(many)m(few):n(many) 的地方运行良好。但不是在您拥有m(many):n(many) 的情况下。这显然会导致 2 个查询和大量的内务处理。

        【讨论】:

        • 这就是我害怕的。假设我将使用 SQL DB 重写整个内容,我仍然会感兴趣是否有人尝试与 mongo DB 建立多对多关系的示例。即使尝试失败,也只是看看在它变坏之前你能真正做到多远。
        猜你喜欢
        • 1970-01-01
        • 2017-10-22
        • 1970-01-01
        • 2017-10-18
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2022-01-24
        相关资源
        最近更新 更多