首页 > 解决方案 > 聚合中的 $lookup 非常慢

问题描述

我正在使用$lookup加入两个集合进行计数,但executionStats显示查询非常慢(单个查询每个查询 500+ms)。这是集合大小:

> db.visit.find({}).count()
8174
> db.links.find({}).count()
89

这是聚合的查找策略:

> db.visit.explain('executionStats').aggregate([{"$lookup": {from: "links", localField: "alias", foreignField: "alias", as: "url"}}])

结果是:

{
    "stages": [
        {
            "$cursor": {
                "queryPlanner": {
                    "plannerVersion": 1,
                    "namespace": "redir.visit",
                    "indexFilterSet": false,
                    "parsedQuery": {

                    },
                    "queryHash": "8B3D4AB8",
                    "planCacheKey": "8B3D4AB8",
                    "winningPlan": {
                        "stage": "COLLSCAN",
                        "direction": "forward"
                    },
                    "rejectedPlans": []
                },
                "executionStats": {
                    "executionSuccess": true,
                    "nReturned": 8174,
                    "executionTimeMillis": 642,
                    "totalKeysExamined": 0,
                    "totalDocsExamined": 8174,
                    "executionStages": {
                        "stage": "COLLSCAN",
                        "nReturned": 8174,
                        "executionTimeMillisEstimate": 0,
                        "works": 8176,
                        "advanced": 8174,
                        "needTime": 1,
                        "needYield": 0,
                        "saveState": 9,
                        "restoreState": 9,
                        "isEOF": 1,
                        "direction": "forward",
                        "docsExamined": 8174
                    }
                }
            },
            "nReturned": NumberLong(8174),
            "executionTimeMillisEstimate": NumberLong(7)
        },
        {
            "$lookup": {
                "from": "links",
                "as": "url",
                "localField": "alias",
                "foreignField": "alias"
            },
            "nReturned": NumberLong(8174),
            "executionTimeMillisEstimate": NumberLong(643)
        }
    ]
}

为什么以及如何解决它?

标签: mongodbaggregation-framework

解决方案


该管道在第一阶段没有任何过滤,因此当您执行聚合时,隐含的第一步是“从访问集合中获取所有文档”

然后将每个文档传递到管道中,该$lookup阶段对每个文档的“链接”集合执行查找查询。

如果您在 links 集合中没有索引{alias: 1},则它必须检查该集合中的每个文档以从源文档中获取与“别名”字段匹配的任何文档。

在未索引的情况下,这意味着聚合查询将需要检查这 89 个链接文档中的每一个文档 8174 次,总共需要 727486 个文档检查。每次检查都将涉及每个文档中 1 个字段的字符串比较(假设“别名”是一个字符串)。

总运行时间为 643 毫秒,平均每毫秒检查约 1131 个文档,这意味着每微秒 1 个字符串比较。

这实际上听起来并不算太糟糕。

如果您希望它执行得更快,请尽量减少检查的文档数量。

有两种主要方法可以实现这一点:

  • 不要使用别名,将链接信息直接放在“访问”文档中。这将完全消除在获取期间进行 $lookup 的需要,但代价是在编写“访问”文档时进行查找
  • 在“链接”集合中创建一个索引{ alias: 1 }这将替换 89 * 8174 文档检查和 8174 索引扫描,然后仅检查具有匹配别名的文档。假设每个“访问”仅包含 1 个别名,并且每个别名仅存在于一个“链接”文档中,这会将总数从 727486 个文档检查减少到 8174 个索引扫描 + 8174 个文档检查。

推荐阅读