MongoDB - 与一个字段匹配,并获取在另一个字段中具有不同值的第一条记录?

问题描述 投票:0回答:1

我正在寻找一个非常高性能的 MongoDB 查询,它将根据字段“matchField”进行匹配,并且仅返回具有明显不同字段“batchId”的第一个文档。

给定一个包含以下文档的集合:

{
  "matchField": "ABC",
  "batchId": "123",
  "documentNumber": 1,
  ...rest of document
},
{
  "matchField": "XYZ",
  "batchId": "123",
  "documentNumber": 2,
  ...rest of document
},
{
  "matchField": "ABC",
  "batchId": "345",
  "documentNumber": 3,
  ...rest of document
},
{
  "matchField": "ABC",
  "batchId": "123",
  "documentNumber": 4,
  ...rest of document
},

对于这些文档,在这个阶段,我需要的只是元数据,它基于“batchId”将是相同的

现在我有了这样的查询所需的功能

db.myCollection.aggregate([
  {"$match": {"matchField": "ABC"}},
  {"$group": {_id: "$batchId"},
     "myMetadata1": {"$first": "$myMetadata1"}
     ... get all the fields I need
  }
]);

这为我提供了文档 1 和 3 中所需的元数据。我没有获得文档 2,因为它与 matchField 不匹配。我没有得到文档 4,因为我已经得到了一个 batchId 为“123”的文档。

问题是,它的性能极差。尽管我只想要这 2 条记录,但当我有 2000 万条记录时(即使所有内容都已正确索引),也可能需要几分钟的时间。我相信这是因为存储引擎仍在正确地对所有 2000 万条记录进行分组,并为每条记录运行 $first 逻辑,即使我只关心与我的查询匹配并具有不同的 batchId 的任何给定记录的数据。

有什么方法可以让这种行为具有合理的性能吗?看起来很简单,但我很挣扎

mongodb aggregation-framework
1个回答
0
投票

即使所有内容都已正确索引

与其在这里声称正确性,不如您说明索引的实际内容,这将对我们有利。如果您分享该操作的

.explain("executionStats")
输出,我们就可以明确了解数据库如何实际执行该操作,这是额外的奖励点,甚至会更有帮助。

我相信这是因为存储引擎仍在正确地对所有 2000 万条记录进行分组,并为每条记录运行 $first 逻辑,尽管我只关心与我的查询匹配并具有不同的 batchId 的任何给定记录的数据.

这个猜测很好,我也有同样的怀疑。但如果没有上述的

explain
计划,我们只是猜测。

我正在寻找一个非常高性能的 MongoDB 查询...

有什么方法可以让这种行为具有合理的性能吗?

这里的答案部分取决于在这种情况下“表演者”的定义是什么。您提到目前需要几分钟,您的目标持续时间是多少?

答案的另一个考虑因素取决于其他因素。也许值得注意的是 - 您从正在处理的集合中的这 2000 万个源文档中获得了多少个不同的分组(输出文档)?


建议

沿着@ray 评论的内容,值得仔细检查该集合是否确实为此聚合正确索引。这里的目标应该是确认您是否正在利用 优化来返回每组的第一个文档

正如已经指出的,在您的情况下启用此优化的最重要的事情是具有以下复合索引:

{ "batchId": 1, "matchField": 1 }

我们可以看出,由于"stage": "DISTINCT_SCAN"输出中存在

explain
,我们已经成功触发了
这个游乐场示例
中的优化。通过聚合折叠的重复
batchId
越多,我认为这种方法就越有利。

您还提到您在分组时通过此操作收集元数据。如果只有少数字段或文档的其余部分很大(或者您只是想尝试一下),您可以更进一步并扩展索引以使其完全覆盖操作。这可以通过在

$first
阶段附加您通过
$group
收集的任何其他字段来完成。仅使用描述中引用的
myMetadata1
字段作为示例,较大的索引将为:

{ "batchId": 1, "matchField": 1, "myMetadata1": 1 }

这样,关联的

explain
输出就会放弃对
"FETCH"
阶段的引用,取而代之的是
"PROJECTION_COVERED"
阶段。

© www.soinside.com 2019 - 2024. All rights reserved.