【问题标题】:MongoDB efficient dealing with embedded documentsMongoDB高效处理嵌入文档
【发布时间】:2014-08-23 16:02:40
【问题描述】:

我很难在 Mongo 文档中找到有关处理嵌入式文档的任何有用信息。假设我有以下架构:

{
  _id: ObjectId,
  ...
  data: [
    {
      _childId: ObjectId // let's use custom name so we can distinguish them
      ...
    }
  ] 
}
  1. 删除data 中所有内容的最有效方法是什么? 特别是_id?

  2. 删除嵌入文档的最有效方法是 给定_id 内的特定_childId?有什么表现 在这里,可以对_childId 进行索引以实现对数(或 类似)复杂性而不是线性查找?如果有,怎么做?

  3. 插入大量(假设是 1000)的最有效方法是什么 对于给定的_id,将文档放入data?和上面一样,我们能得到 O(n log n) 或具有适当索引的类似复杂度?

  4. 对于给定的_id,获取data 中的文档计数的最有效方法是什么?

【问题讨论】:

  • 一次只问一个问题效果最好。

标签: performance mongodb


【解决方案1】:

其他两个答案对您的问题 1-4 提供了明智的建议,但我想通过首先询问您的问题来解决您的问题。在 MongoDB 存储“文档”的上下文中,“嵌入式文档”的术语使人们感到困惑。您不应该将嵌入式文档视为 MongoDB 中您搜索、索引或更新的另一个文档,因为它不是它自己的文档。它是文档中字段的分组集合;这是type Object 的 BSON 字段。引用embedded document docs

嵌入式数据模型允许应用程序将相关的信息片段存储在同一数据库记录中。因此,应用程序可能需要发出更少的查询和更新来完成常见操作。

从了解您的用例开始,您应该选择您的文档和文档结构,以使您的常见操作更容易。如果您非常关心 1-4,您可能希望将您的 data 数组 childIds 展开到单独的文档中。这种常见“反模式”的一个具体例子是一个有许多作者的博客——你可以有一个用户文档,其中嵌入了大量的、不断变化的帖子数组,或者一个帖子文档,每个文档都复制了用户信息。我不能确定您的数据模型有什么问题或没有问题,因为您没有提供有关它的具体细节,但是努力理解为什么 1-4 在 MongoDB 中看起来很难、没有记录或速度很慢是一个好兆头您应该重新考虑数据模型,以便 1-4 的等价物既有趣又简单!或者至少更简单、更有趣。

【讨论】:

    【解决方案2】:

    我找不到任何关于速度的内容,因此我将采用文档中的方法,希望他们采用他们记录的最有效的方法:

    1. 如果要删除数据中的所有子文档,只需将 data 更新为 []

    2. 从数据中删除带有特定_childId 的文档的官方方法是$pull

      db.collection.update(
          { },
          { $pull: { data: { _childId: id } } },
      )
      

      如果_childId 不是唯一的(多部分子文档),可能需要添加{ multi: true }

      关于子文档的索引,我建议您参考this question。简短的回答是的,您可以索引子文档中的字段以便更快地查找,就像您索引普通字段一样

      db.collection.ensureIndex({"data._childId" : 1})
      

      如果您只想在一个特定文档中搜索子文档,您可以使用aggregation,即

      db.collection.aggregate({$match:{_id : _id},
                              {$unwind:'$data'},
                              {$match:{data._childId: _childID})
      

      首先匹配_id,然后才匹配_childId。它将返回父文档,其数据仅包含 _childId 的子文档。

    3. $push,尽管对于 1000 个子文档,您可能不想在一个查询中这样做

    【讨论】:

    • 在 1) 我问过_id 而不是_childId。至于 2) 你提到的索引是在集合级别,而不是文档级别,所以像 { _id: 123, data._childId: 120 } 这样的 find() 查询也将扫描除 123 之外的 _id 条目(尽管它不会返回它们)。还是谢谢。
    • @Sebastian 已相应编辑
    • 谢谢,但是.aggregate 方法要么不使用索引,要么仍然需要扫描不匹配的_id
    • 没错,但如果您查询单个特定子文档,通常使用引用可能比嵌入它们更好。然后,您可以通过索引(可能通过 _id 和 _parentId 的组合索引)查询(子)文档,并在需要时使用聚合构建父文档
    【解决方案3】:
    1. Trudbert 是对的:db.collection.update({_id:yourId},{$set:{data:[]}})
    2. Trudbert 得两分。但是,我想补充一点,如果您的应用中有整个文档可用,那么在适合您的用例的情况下,简单地替换整个文档的内容可能是合理的。
    3. 我在bulk updates 性能方面取得了很好的经验。您可能想尝试一下。
    4. 我不知道你是怎么想到聚合不使用索引的,但由于 _id 是唯一的,使用 db.collection.findOne({_id:yourId},{"data._childId":1,_id:0}).data.length 或将其用作原始命令会更有意义在选择的驱动程序中。由于连接已经建立,除非数组非常大,否则直接返回数据而不是在可能(过载)的服务器上完成计算应该更快。

    根据您对 Trudberts 的回答:_id 是独一无二的。因此,需要针对已知 _id 修改一个文档:db.collection.update({_id:theId},{$pull....。它并没有变得更有效率。对于未知 id,在 childId 上创建一个索引,并在 childId 上而不是 id 上执行相同的拉取操作,并设置 multi 选项以删除对特定 childId 的所有引用。

    我强烈支持 Trudberts 的建议,即在需要时使用聚合框架从优化数据中创建文档。目前,我有一个聚合管道,它可以在大约 6 秒内分析 5M 条记录,它们之间的关系超过 700 万条。在非分片独立实例上。使用旋转磁盘,糟糕的 IO 甚至没有优化。通过仔细规划聚合(早期匹配限制传递给迄今为止未处理的文档)并将它们与早期结果合并(在 group 阶段调整 _id 可以实现这一点),您甚至可以针对其中的一小部分进行优化秒,如果绝对必要的话。

    【讨论】:

    • 如果数组很大,use it's equivalent as a raw command in the driver of choice 的可用策略是什么?
    • 正如我所说:除非它很大。我刚刚用数组的一些 400k 条目对其进行了测试,非常接近 16MB 的最大 BSON 大小({b:[{c:1,text:"foo"},...,{c:400000,text:"foo"}}]}。该命令平均需要 2.5 秒。在这种情况下,使用聚合管道更快。实际上,必须根据用例找出哪个命令显示更好的性能。就个人而言,我会说使用聚合管道创建统计表是我个人采用的方式。
    • 如何通过调用db.eval() 来实现该命令?我无法理解 mongodb 不会将数组的大小存储在数组的 BSON 结构的头部。如果是这样,那么很容易获得任何数组的大小。你知道为什么吗?
    • 我不知道您使用的是什么语言,但它记录在相应的驱动程序 API 文档中。至于数组头中的大小:mongod 必须在每次对数组执行写入操作时计算数组的大小。由于数组元素的大小可能有很大差异,因此 mongod 基本上每次写入时都必须解析完整的数组,这将导致写入效率非常低。它现在可以做的是在文档中查找\x04FieldName,找到下一个空字节,然后在填充中或通过重定位来添加数据。相当有效。
    • 我使用的语言是 Java。非常感谢详细的解释。也许我用了一个不恰当的词“大小”。我的意思是针对问题 4) 的数组中元素的计数。我们知道在这种模式下保存 BSON 数组:[elements]。如果数组更改,则必须修改 。那么,为什么不改变模式:[elements]。即使您尝试了 400K 条目,也无需额外计算即可立即获得数组元素的数量。有什么想法吗?
    猜你喜欢
    • 1970-01-01
    • 2021-10-04
    • 1970-01-01
    • 2020-04-23
    • 2018-08-21
    • 2012-08-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多