刚接触MongoDB进入NoSQL Realm 之后,我仍在努力理解最佳的架构方法,而无需先对数据进行第三次标准化然后再加入。目前,我正在设计的项目是一个类似于Wiki的简单文章集。一篇文章将具有标题和文本,以及(可能)一篇父文章和一个或多个子文章。
对于数据库设计,我有很多不同的想法,并希望选择最适合MongoDB优势的想法。
创意一
由于数据库上最常见的查询类型总是简单地检索文章,因此我将嵌入页面显示所有内容所需的所有相关数据。当然是实际的文章,以及带有 url (将与其他文档的_id匹配)以及标题的父子文档,这是我们将在屏幕上打印出的文本标签。除子代是一个数组外,它存在一个标识结构,以便所有子代都存在。
{
"_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 。
为了获得查看文章的所有信息,我们现在需要进行许多查询(至少我认为我们需要...)一个获取主要文章,然后另一个获取 parent 的书名。用于放置标签,然后再放置以获取所有子文章,并同样获取其标题。
这似乎只是为了显示文章而进行的大量查询,但是,例如,如果某些文章被删除或更新,则可能使数据库的更新更加容易。 (再次不确定这一点)。
{
"_id" : "test-article-2",
"title" : "Test Article 2",
"text" : "Blah 2",
"parent" : "test-article",
"children" : [ "test-article-3", "test-article-4"]
}
很高兴听到有更多MongoDB设计经验的人的意见。
您需要考虑将要执行的查询的类型以及每种类型需要的频率。当我从事类似工作时,我想出了六种可能的措施:
与父代做些事情
与 child 一起做些事
与祖先( parent 的 parent , parent 的 parent 的 parent 等)做些事情
与后代( child 的 child , child 的 child 的 child 等)做些事
更改关系(在层次结构中添加/移动/删除节点)
更改当前节点中的主数据(例如更改“title”字段中的值)
您将要估计每一个对您的应用程序的重要性。
如果您的大部分工作涉及处理某些给定文章(包括其直接父级和子级)的存储数据,那么的第一个想法最有用。实际上,在MongoDB中,将所需的所有信息放在同一文档中而不是在外部引用它是很常见的,因此您只需要检索一件事并使用该数据即可。不过,列表中的最后四个 Action 比较棘手。
特别是,在这种情况下,即使您只在乎路径中的最后一个文档,在这种情况下,您仍需要遍历树以检索祖先和后代,并在中间文档中移动并遵循路径。对于长层次结构,这可能会很慢。由于每个文档中都存在所有数据,因此更改关系可能需要在多个文档中移动大量信息。但是,即使更改单个字段(如“标题”)也可能很烦人,因为您必须考虑以下事实:该字段存在于多个不同的文档中,无论是作为主字段还是在父或子字段下。
基本上,您的第一个想法在更静态的应用程序中效果最佳,在这些应用程序中,最初创建数据后您不会对其进行太多更改,但是需要定期读取数据。
MongoDB文档的five recommended approaches用于处理树状(分层)结构。它们都具有不同的优点和缺点,尽管它们都使得仅需在一个文档中进行更新就可以轻松更新文章中的主要数据。
父级引用:每个节点都包含对其父级的引用。
优点:
快速父查找(通过“_id” =您的文档标题查找,返回“父”字段)
快速子查询(“父”查询=您的文档标题,它将返回所有子文档)
更新关系只是更改“父”字段的问题
更改基础数据只需要更改一个文档
缺点:
祖先和后代的搜索速度很慢,需要遍历
子引用:每个节点都包含对其子级的引用数组
优点:
快速检索子项(返回children数组)
快速关系更新(仅在需要时更新子级数组)
缺点:
要找到父级,需要在所有文档的所有子级数组中查找_id,直到找到为止(因为父级将当前节点作为子级包含)
祖先和后代搜索需要遍历树
祖先数组:每个节点都包含对其祖先及其父级数组的引用
优点:
快速检索祖先(无需遍历即可找到特定的个体)
遵循“ parent 引用”方法,易于查找 parent 和子女
要查找后代,只需查找祖先,因为所有后代都必须包含相同的祖先
缺点:
当关系发生更改时(通常跨多个文档),需要担心保持祖先数组以及父字段的更新。
物化路径:每个节点都包含一个指向其自身的路径-需要正则表达式
优点:
使用正则表达式容易找到 child 和后代
可以使用路径来检索父代和祖先
灵活性,例如通过部分路径查找节点
缺点:
关系更改很困难,因为它们可能需要更改多个文档中的路径
嵌套集:每个节点都包含一个“left”和“right”字段,以帮助查找子树
优点:
通过在“left”和“right”之间搜索,以最佳方式轻松检索后代
类似于“ parent 引用”方法,很容易找到 parent 和 child
缺点:
需要遍历结构以查找祖先
关系更改在这里比其他任何选项都表现最差,因为一旦更改了层次结构,可能需要更改树中的每个文档以确保“left”和“right”仍然有意义
在
MongoDB documentation中将更详细地讨论这五种方法。
您的
第二个想法结合了上面讨论的“父级引用”和“子级引用”方法。这种方法使查找子项和父项变得容易,并且易于更新文章的关系和主要数据(尽管您需要同时更新父项和子项字段),但仍然需要遍历它寻找祖先和后代。
如果您对查找祖先和后代感兴趣(并且比起能够轻松地更新关系更在意此事),可以考虑在第二个想法中添加一个祖先数组,以方便查询祖先和后代。当然,如果您这样做,更新关系将成为真正的痛苦。
结论:
最终,这完全取决于最需要采取哪些措施。由于您正在处理的文章的基础数据(例如标题)可能会经常更改,因此您可能要避免使用第一个主意,因为不仅需要更新该文章的主文档,还需要更新所有子文档以及子文档。 parent
您的第二个想法使检索直接的 parent 和子女变得容易。更新关系也不是那么困难(肯定比其他可用选项要好)。
如果您真的想以轻松地更新关系为代价来轻松地找到祖先和后代,请选择包括一系列祖先引用。
通常,请尝试最小化所需的遍历次数,因为它们需要运行某种迭代或递归才能获取所需的数据。如果您重视更新关系的能力,则还应该选择一个选项,以更改树中更少的节点(“父引用”,“子引用”,第二个想法可以做到这一点)。
我是一名优秀的程序员,十分优秀!