MongoDB - 子和父结构

时间:2014-11-12 16:12:12

标签: mongodb database-design nosql

刚刚使用MongoDB深入研究NoSQL的世界,我仍然在努力理解最佳的架构方法,而不是先对数据进行规范化,然后加入它。目前我正在设计的项目是一个简单的文章集,类似于维基。一篇文章将有一个标题和文本,以及(可能)一篇父文章和一篇或多篇儿童文章。

我对数据库设计有很多不同的想法,并希望选择最符合MongoDB强度的那个。

Idea One

由于数据库上最常见的查询类型总是简单地检索文章,因此我将嵌入页面所需的所有相关数据以显示所有内容。当然,实际的文章,以及带有 url 的父子文档(与其他文档的_id匹配)以及 title 这是一个文本我们将在屏幕上打印出标签内部。 孩子存在一个身份结构,但它是一个数组,以便所有孩子都存在。

{
        "_id" : "test-article-2",
        "title" : "Test Article 2",
        "text" : "Blah 2",
        "parent" : {
                "title" : "Test Article",
                "url" : "test-article"
        },
        "children" : [
                {
                        "title" : "Test Article 3",
                        "url" : "test-article-3"
                }
        ]
}

这种类型的设计似乎具有速度的优势(在我看来),但我想听听其他设计的内容。

理念二

更多的是我习惯来自关系数据库世界。不是将子对象嵌入到设计中,而是简单地放入它们的唯一标识符。因此,现在只包含一个与其他文档的 _id 匹配的文本字符串,同样会包含一个链接数组到 _id s。

为了获得查看文章的所有信息,我们现在需要进行一些查询(至少我认为我们需要...)一个获取主要文章,然后另一个获取父母的标题放入标签,然后是另一个获得所有儿童文章,同样获得他们的标题。

这似乎只是为了显示一篇文章而进行更多的查询,但是如果删除或更新了一些文章,它可能会使数据库的更新变得更容易。 (再次不确定那一点)。

{
        "_id" : "test-article-2",
        "title" : "Test Article 2",
        "text" : "Blah 2",
        "parent" : "test-article",
        "children" : [ "test-article-3", "test-article-4"]
}

很高兴听到那些有更多MongoDB设计经验的人的意见。

2 个答案:

答案 0 :(得分:19)

您需要考虑需要执行的查询类型以及每种类型需要的频率。当我在做类似的事情时,我提出了六个可能的行动:

  • 与父母做点什么
  • 与孩子们做点什么
  • 与祖先做父母的事(父母的父母,父母的父母等)
  • 与后代(孩子的孩子,孩子的孩子等)做点什么。
  • 更改关系(在层次结构中添加/移动/删除节点)
  • 更改当前节点中的主要数据(例如,更改"标题"字段中的值)

您想要估算这些对您的应用程序的重要性。

如果您的大部分工作涉及使用某些特定文章(包括其直接的父母和子女)的存储数据,第一个想法最有用。实际上,在MongoDB中,将所需的所有信息放在同一文档中而不是在外部引用它是很常见的,这样您只需要检索一件事并只使用该数据。列表中的最后四个动作虽然更棘手。

特别是,在这种情况下,您将需要遍历树以检索祖先和后代,移动中间文档并遵循路径,即使您可能只关心路径中的最后一个文档。对于长层次结构,这可能很慢。改变关系可能需要在多个文档中移动大量信息,因为每个文档中存在所有数据。但即使改变像#34; title"可能很烦人,因为您必须考虑这个字段存在于多个不同文档中的事实,无论是作为主要字段还是在父字段或子字段下。

基本上,您的第一个想法在更多静态应用程序中效果最佳,在最初创建数据后,您不会在很多时候更改数据,但您需要在哪里定期阅读。

MongoDB文档具有five recommended approaches,用于处理树状(分层)结构。所有这些都有不同的优点和缺点,尽管它们只需要在一篇文档中更新主要数据就可以轻松更新。

  • 父参考:每个节点都包含对其父级的引用。
  • 优点
    • 快速查找父母(按&#34查找; _id" =您的文档标题,返回"父母"字段)
    • 快速查找子项(按&#34查找;父级" =您的文档标题,将返回所有子文档)
    • 更新关系只需更改父母"字段
    • 更改基础数据只需要更改一个文档
  • 缺点
    • 搜索祖先和后代的速度很慢,需要遍历
  • 子引用:每个节点都包含一个引用其子节点的数组
    • 优点
      • 快速检索孩子(返回子阵列)
      • 快速关系更新(只需更新儿童的数组)
    • 缺点
      • 查找父级需要在所有文档的所有子数组中查找_id,直到找到它为止(因为父级将包含当前节点作为子级)
      • 祖先&后代搜索需要树的遍历
  • 祖先数组:每个节点都包含对其祖先数组的引用。它的父母
    • 优点
      • 快速检索祖先(找不到特定的搜索所需的遍历)
      • 根据"家长参考"轻松查找父母和孩子。方法
      • 要查找后代,只需查看祖先,因为所有后代必须包含相同的祖先
    • 缺点
      • 当关系发生变化时,需要担心保持祖先数组以及父字段更新,通常跨多个文档。
  • 物化路径:每个节点都包含一个自身路径 - 需要正则表达式
    • 优点
      • 使用正则表达式轻松找到孩子和后代
      • 可以使用路径检索父级和祖先
      • 灵活性,例如通过部分路径查找节点
    • 缺点
      • 关系变更很困难,因为它们可能需要更改多个文档中的路径
  • 嵌套集:每个节点都包含一个"左边"和"对"字段,以帮助查找子树
    • 优点
      • 通过在" left"之间搜索,以最佳方式轻松检索后代。和"对"
      • 喜欢"家长参考"方法,很容易找到父母和孩子
    • 缺点
      • 需要遍历结构才能找到祖先
      • 这里的关系更改比其他任何选项都要糟糕,因为树中的每个文档都需要更改以确保" left"和"对"一旦层次结构发生变化,仍然有意义

MongoDB documentation中详细讨论了这五种方法。

第二个想法结合了"家长参考"和儿童参考"儿童参考"上面讨论的方法。这种方法可以很容易地找到子项和父项,并且可以轻松更新文章的关系和主要数据(尽管您需要更新父项和子项),但仍需要遍历它寻找祖先和后代。

如果您有兴趣寻找祖先和后代(并且关心这一点而不是能够轻松更新关系),您可以考虑在您的第二个想法中添加祖先数组,以便查询祖先和后代。当然,如果你这样做,更新关系会变得非常痛苦。

<强>结论:

  • 最终,这一切都取决于最需要的行动。由于您正在处理文章,其基础数据(如标题)可能经常更改,您可能希望避免第一个想法,因为您不仅需要更新该文章的主文档,还需要更新所有子文档作为父母。

  • 您的第二个想法可以轻松检索直接的父母和孩子。更新关系也不是太困难(它肯定比其他一些可用的选项更好)。

  • 如果您真的希望以牺牲更新关系为代价来轻松找到祖先和后代,请选择包含一系列祖先引用。

  • 通常,尽量减少所需的遍历次数,因为它们需要运行某种迭代或递归来获取所需的数据。如果您重视更新关系的能力,您还应该选择一个更改树中较少节点的选项(父引用,子引用,您的第二个想法可以执行此操作)。

答案 1 :(得分:1)

来自@CynicalProgrammer的非常好的总结,我还要补充一点:使用json是你的冒险树的事实!无需在每个节点中仅存储“左”和“右”节点ID,为什么不存储子树3-4-5深度?所以它看起来像这样:

{
left: {
    left: {
        left: {...},
        right: {...}
    },
    right: {...}
},
right: {... you get the idea ...}
}

通过这种方式,您需要更少的查询来遍历树,并且集合中的文档数量将是一小部分。一个小缺点是你需要一些更复杂的代码来写入树,mongodb文件会更大,这意味着个别写入速度较慢。

我认为这可能是在mongodb中储存树木的最佳方式。但是请记住,mongo用于文档存储,而不是“大屁股无模式树”存储。您可能想要查看类似neo4j的内容。