【问题标题】:Most efficient way to store nested categories (or hierarchical data) in Mongo?在 Mongo 中存储嵌套类别(或分层数据)的最有效方法?
【发布时间】:2013-02-19 20:29:47
【问题描述】:

我们为多个产品(例如,体育 -> 篮球 -> 男装体育 -> 网球 -> 女装)嵌套了类别,并且使用 Mongo 而不是 MySQL .

我们知道如何在 MySQL 等 SQL 数据库中存储嵌套类别,但如果您能就如何为 Mongo 提供任何建议,我们将不胜感激。我们需要优化的操作是快速找到一个类别或子类别中的所有产品,这些产品可以嵌套在一个根类别下的几层(例如,Men's Basketball 类别中的所有产品或女子网球类别)。

This Mongo doc 提出了一种方法,但它表示当我们需要对子树进行操作时,这种方法效果不佳(因为类别可以达到多个级别)。

关于有效存储和搜索任意深度嵌套类别的最佳方法有什么建议吗?

【问题讨论】:

  • 物化路径查询能力强,更新速度慢
  • mongodb 文档链接列出了五种方法,没有一种,我认为第三种方法似乎非常适合您的用例。

标签: mongodb database nosql


【解决方案1】:

您要决定的第一件事就是您将使用哪种树。

要考虑的最重要的事情是您的数据和访问模式。您已经说过,您所有工作的 90% 都将是查询,并且听起来(电子商务)更新只会由管理员运行,而且很可能很少。

因此,您需要一个模式,让您能够通过路径快速查询孩子,即:体育 -> 篮球 -> 男子、体育 -> 网球 -> 女子,并且不需要真正扩展到更新。

正如您正确指出的那样,MongoDB 确实为此提供了一个很好的文档页面:https://docs.mongodb.com/manual/applications/data-models-tree-structures/ 其中 10gen 实际上为树声明了不同的模型和模式方法,并描述了它们的主要起伏。

如果您希望轻松查询,应该引起注意的是具体化路径:https://docs.mongodb.com/manual/tutorial/model-tree-structures-with-materialized-paths/

这是一种非常有趣的构建树的方法,因为要在上面给出的示例中查询“网球”中的“女性”,您可以简单地执行一个预先固定的正则表达式(可以使用索引:http://docs.mongodb.org/manual/reference/operator/regex/)像这样:

db.products.find({category: /^Sports,Tennis,Womens[,]/})

查找树的特定路径下列出的所有产品。

不幸的是,这种模型在更新方面确实很糟糕,如果您移动一个类别或更改其名称,您必须更新所有产品,并且一个类别下可能有数千种产品。

更好的方法是在产品上放置cat_id,然后使用架构将类别分成单独的集合:

{
    _id: ObjectId(),
    name: 'Women\'s',
    path: 'Sports,Tennis,Womens',
    normed_name: 'all_special_chars_and_spaces_and_case_senstive_letters_taken_out_like_this'
}

所以现在您的查询只涉及类别集合,这应该会使它们更小且性能更高。例外情况是,当您删除一个类别时,产品仍需要触摸。

所以一个把“网球”改成“羽毛球”的例子:

db.categories.update({path:/^Sports,Tennis[,]/}).forEach(function(doc){
    doc.path = doc.path.replace(/,Tennis/, ",Badmin");
    db.categories.save(doc);
});

不幸的是,MongoDB 目前没有提供查询内文档反射,因此您必须将它们拉出客户端,这有点烦人,但希望它不会导致带回太多类别。

这基本上就是它真正的工作方式。更新有点痛苦,但我相信能够使用索引在任何路径上即时查询的能力更适合您的场景。

当然,额外的好处是这个模式与嵌套集合模型兼容:http://en.wikipedia.org/wiki/Nested_set_model 我一次又一次地发现它对于电子商务网站来说非常棒,例如,网球可能在两个“运动”下和“休闲”,并且您需要多个路径,具体取决于用户来自哪里。

物化路径的架构很容易支持这一点,只需添加另一个path,就这么简单。

希望它是有道理的,那里很长。

【讨论】:

  • 谢谢!如果我们想要存储类别元信息(例如,名称和 ID)怎么办?我们是否应该为类别留出单独的集合,然后在产品的类别路径中使用 ID?我们预计类别信息不会经常更改,可能一年一次。
  • @Crashalot 是的,如果将某些内容分配给类别,通常最好将其存储在类别中,另一种方法是将其存储在每个产品中,即使它不会经常更改,但当除了产品之外,您还将获得想要获取其元数据的类别
  • 酷,感谢您的确认。这也表明我们将类别 ID 存储在路径中,而不是类别名称。你觉得这有什么不妥吗?您也有在 Mongo 中存储和查询分层数据的经验吗?只是好奇你是否对一个小型咨询项目感兴趣。 :)
  • @Crashalot 唯一的事情是,要了解要查询的路径,您需要从某个地方提取类别的 _id,无论是路径中的每个类别还是产品中的每个类别,但是如果你做一个面包屑,你很可能会拉出这些类别,所以我不认为有问题。
  • @Crashalot 我确实有相当多的经验,我在其中为我的 MongoDB 视频网站制作了一个 Google 帮助系统,它运行良好,还完成了一些其他需要在 MongoDB 中使用树的项目
【解决方案2】:

如果所有类别都是不同的,则将它们视为标签。不需要在项目中编码层次结构,因为在查询项目时不需要它们。层次结构是一种表现形式。用其路径中的所有类别标记每个项目,因此“运动>棒球>鞋子”可以保存为{..., categories: ["sport", "baseball", "shoes"], ...}。如果您想要“运动”类别中的所有商品,请搜索 {categories: "sport"},如果您只想要鞋子,请搜索 {tags: "shoes"}

这并没有捕捉到层次结构,但是如果您考虑一下,这并不重要。如果类别不同,则在查询项目时层次结构对您没有帮助。不会有其他“棒球”,因此当您搜索它时,您只会得到层次结构中“棒球”级别以下的内容。

我的建议依赖于不同的类别,我猜它们不在您当前的模型中。但是,没有理由不能区分它们。您可能已选择将页面上显示的字符串用作数据库中的类别名称。如果您改为使用“sport”或“womens_shoes”等符号名称并使用查找表来查找要在页面上显示的字符串(如果类别名称发生变化,这也将为您节省数小时的工作时间——而且它会使网站的翻译更容易,如果您需要这样做)您可以轻松地确保它们是不同的,因为它们与页面上显示的内容没有任何关系。因此,如果层次结构中有两个“鞋”(例如“网球 > 女式 > 鞋”和“网球 > 男式 > 鞋”),您只需添加一个限定符以使它们不同(例如“womens_shoes”和“mens_shoes” , 或 "tennis_womens_shoes") 符号名称是任意的,可以是任何东西,您甚至可以使用数字,每次添加类别时只需使用序列中的下一个数字。

【讨论】:

  • 你的答案的最后一部分使用这样的限定词很像物化路径,只是它对其感知的深度和形成没有真正的标准化,有些人可能认为这在这方面很糟糕。跨度>
  • 绝对不是物化路径,我不建议符号名称应该包含完整的层次结构,它们可以是完全任意的。我的示例仅包括部分层次结构,因为标签非常通用。它们应该尽可能具体,但仅此而已。我相信将层次结构编码到数据库中的项目中是一种反模式。层次结构是一个表示细节,使用物化路径会不必要地重复每个项目的层次结构,使数据模型变得脆弱,并使得以后更改层次结构变得不必要地困难。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-08-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多