MongoDB聚合管道 - 提示和原理
#mongodb #aggregation

介绍

处理数据库时,任何时候需要提取数据,都需要执行一个称为查询的操作。但是,查询仅返回数据库中已经存在的数据。但是,此汇总操作使您可以分析数据为“零”模式或其他有关数据的信息。

mongoDB聚合管道只是一系列指令,称为阶段,可帮助您执行分类,过滤,分组和转换等操作。

为什么

聚合框架是灵活的,用于许多不同的数据处理和操纵任务。一些典型的示例用途是:

  • 分析
  • 实时仪表板
  • 准备通过视图显示的数据
  • 加入来自不同收藏的数据
  • 数据的后处理
  • 隐藏敏感数据的数据掩盖
  • ... 和更多

提示1:何时使用$set$unset$project

当您需要从输入记录中保留大多数字段时,您应该使用这些阶段,并且要从输入中添加,更新或删除一小部分字段。

例如,想象一下有类似于以下的用户文档的集合:

// INPUT  (a record from the source collection to be operated on by an aggregation)
{
   _id: ObjectId("6044faa70b2c21f8705d8954"),
   first_name: "Maurice",
   last_name: "Moss",
   email: "Maurice.Moss@gmail.com",
   registered: true,
   dob: "1983-08-31T23:59:59.736Z",
   age:39,
   address: {
       street: "123 Fake St",
       city: "Springfield",
       state: "IL",
       zip: "12345"
   }
 }

然后,想象一下要生成文档的修改版本需要进行聚合管道,如下所示:

// OUTPUT  (a record in the results of the executed aggregation)
 {
   name: "Maurice Moss", // Added a new name field
   first_name: "Maurice",
   last_name: "Moss",
   email: "Maurice.Moss@gmail.com",
   registered: true,
   dob: ISODate("1983-08-31T23:59:59.736Z"), // Field type converted from text
   age:39,
   address: {
       street: "123 Fake St",
       city: "Springfield",
       state: "IL",
       zip: "12345"
   }
 }

在这里,以//注释显示,需要稍微修改每个文档的结构,以将dob文本字段转换为适当的日期字段,并添加一个新的name字段,设置为“ first_name + last_name”,对于每个记录。

天真地您可能决定使用$project阶段构建聚合管道以实现此转换,这可能看起来与以下几个相似:

//BAD
[
  {
      $project: {
          // Modify a field + add a new field
          name: { $concat: [ "$first_name", " ", "$last_name" ] },
          dob: { $dateFromString: {"dateString": "$dob"} },

          // Must now name all the other fields for those fields to be retained
          email: 1,
          registered: 1,
          age: 1,
          address: 1,

          // Remove _id field
          _id: 0
      }
  }
]

您可以看到,根据输入文档,此管道阶段长度可能会变得很长。由于您使用$project修改或添加字段,因此您还必须从源记录中明确提及彼此的现有字段以包含。想象一下,如果您的源文档有数百个字段!

更好地实现相同结果的方法是使用$set$unset,如下所示:

[
  {$set: {
    // Modified + new field
    name: { $concat: [ "$first_name", " ", "$last_name" ] },
    dob: { $dateFromString: {"dateString": "$dob"} },       
  }},

  {$unset: [
    // Remove _id field
    _id,
  ]},
]

请注意,当您在源文档中有数百个字段时,$set$unset的可容易,您只需要修改其中的几个。

当所需的输出文档的形状与输入文档的形状大不相同时,最好使用$project阶段。当您不需要包含大多数原始字段时,通常会出现这种情况。

提示2:使用解释计划

在编写查询时,重要的是要查看查询的解释计划,以确定您是否使用了适当的索引以及是否需要优化查询。

同样的是聚合管道和查看解释计划的能力。在聚集中,这非常关键,因为您通常倾向于具有更复杂的逻辑。

要查看聚合管道的解释计划,您可以执行诸如以下
之类的命令

const pipeline = [{"$match": {"name": "Jo"}}]
db.users.explain().aggregate(pipeline);

提示3:流媒体与阻塞阶段

执行聚合管道时,数据库引擎从针对源集合生成的初始查询光标中提取记录。然后,它尝试通过聚合管道阶段流式传输每个批次。如果您已经称为_streaming阶段_数据库引擎将处理一批,然后立即将其流式传输到下一阶段。它将无需等待其他批次到达。

但是,有两种类型的阶段必须阻止并等待所有批次到达。因此,它们被称为阻止阶段

  • $ sort
  • $ group

如果您考虑一下,很明显他们必须阻止。因为,如果阶段仅sort/group每个批处理内容而不等待其他批次,则输出记录将在批处理中进行排序,但不能整个结果设置。

这些不可避免的阻塞阶段通过减少并发来增加您的执行时间。如果不小心使用,则不可避免地会大幅度增加记忆消耗和缓慢的吞吐量。

提示4:匹配过滤器出现在管道中

根据MongoDB文档,他们的引擎将尽最大努力在运行时优化管道。特别是,将$match阶段移至管道顶部之类的步骤。

但是,可能并非总是有可能为数据库引擎推广$match过滤器。

有时,在稍后在管道中定义了$match阶段,以在管道在早期阶段计算的字段上执行过滤器。管道的原始输入集合中不存在计算的字段。

因此,您作为开发人员,必须考虑并将$match阶段提升到最高水平,而不是使用早期阶段的计算字段。

感谢您的阅读。您可以在MongoDB文档中找到更多最佳实践。