【问题标题】:Mongodb.aggregate() is ignoring indicesMongodb.aggregate() 忽略索引
【发布时间】:2017-03-21 22:52:13
【问题描述】:

我收集了大致如下结构的归档任务

{
    "_id" : "job-id_00000001_2017-03-17T21:30:38.510Z",
    "jobId" : "job-id",
    "result" : {
        "status" : "ok"
    },
    "..." : "..."
}

除此之外,我还有索引

jobId: 1
result.status: 1
jobId: 1, result.status: 1

在某些用例中,我需要相当频繁地更新统计信息(map: job-id -> status -> count)以及当我执行此聚合函数时...

db.getCollection('jobs_archive').aggregate([
            {$group: {
                _id: {jobId: "$jobId", status: "$result.status"},
                count: { $sum: 1 }
            }}
        ], {explain: true} )

...它在 120 万行上运行约 4 秒,而且这个时间长得令人无法接受。与explain: true 我得到...

"queryPlanner" : {
    "plannerVersion" : 1,
    "namespace" : "db.jobs_archive",
    "indexFilterSet" : false,
    "parsedQuery" : {},
    "winningPlan" : {
        "stage" : "COLLSCAN",
        "direction" : "forward"
    },
    "rejectedPlans" : []
}    

...和 ​​COLLSCAN 意味着 Mongo 不使用索引中的数据,但所有字段都在复合索引 jobId: 1, result.status: 1 中可用。

有没有办法优化aggregate 查询的性能?我做错了吗?


由 Ori Dar 的回答触发的附录

在深入研究文档后,我注意到“涵盖的查询”,在这种情况下应该使用我认为应该使用的功能。似乎不是。

涵盖的查询 https://docs.mongodb.com/manual/core/query-optimization/#covered-query

覆盖查询是可以完全使用 索引并且不必检查任何文档。一个索引涵盖一个 当以下两个都适用时查询:

  • 查询中的所有字段都是索引的一部分,并且
  • 结果中返回的所有字段都在同一个索引中。

...

由于索引包含查询所需的所有字段,MongoDB 既可以匹配查询条件,又可以仅使用返回结果 索引。

仅查询索引可以比查询文档快得多 指数之外。索引键通常小于 他们编目的文档,索引通常在 RAM 或 在磁盘上按顺序定位。


来自 Mongo 的更多怪异

(1) db.getCollection('jobs_archive').find({"jobId" : "job-id"}).count()
--> 0.375sec, count = 430000

(2) db.getCollection('archive').find({"jobId" : "job-id", "result.status": "ok"}).count()
--> 1.400sec, count = 430000

explain()

  1. winningPlan: IXSCAN / "indexName" : "jobId_1_result.status_1"
  2. 获胜计划:IXSCAN / "indexName" : "jobId_1"

所以,如果 Mongo 正确使用索引,我会为每个“job-id+status”组合使用“query().count()”(它是 6 * 5),但它似乎不在这个情况也是如此。当我指定两个键 'jobId + result.status' 复合索引不用于count() ...并且当我在查询中仅指定一个jobId 时,使用复合索引... r-r-r-r

注意:Mongo“版本”:“3.4.2”,Ubuntu 16

【问题讨论】:

    标签: mongodb


    【解决方案1】:

    来自Pipeline Operators and Indexes

    管道运算符和索引¶

    当 $match 和 $sort 管道运算符出现在管道的开头时,它们可以利用索引。

    MongoDB不会使用$group的索引

    在处理所有文档的意义上,您正在执行全面扫描。因此,使用索引会导致对每个文档进行重复查找:一次用于索引,一次用于文档本身,那么有什么意义呢。

    因此,只有先使用$match 过滤器缩小结果范围,才能使用索引。

    附带说明,{jobId: 1} 索引是多余的。

    查询优化器可以将{jobId: 1, result.status: 1} 索引用于使用以下模式的查询:db.jobs_archive.find({jobId: n})

    Prefixes

    【讨论】:

    • 您为什么认为'MongoDB 不会为$group 使用索引'?我浏览了他们的文档并没有找到这样的声明。相反,我发现了关于“覆盖查询”的注释(请参阅我的问题中的附录)——这正是我的情况,但它似乎由于某种原因不起作用。
    • 因为我从参考文档中引用的声明正是如此。而且您没有使用涵盖的查询。要使用覆盖查询,您必须在确切的索引字段上 $match 并排除 _id 字段
    • 运行类似db.jobs_archive.explain().aggregate({$match: {jobId: 1}},{$group: {_id: {jobId: "$jobId", status: "$result.status"}, count: { $sum: 1 }}})的东西,你会看到索引在起作用
    • 您在文档中的引用仅与$match$sort 相关,与$group 无关。为了聚合一个“文档”,Mongo 需要获取它,因为所有需要的字段都已经在复合索引中 - Mongo 可以使用它,如 Covered Queries 中所述。 $match 实际上对我来说毫无用处,因为我需要对整个集合进行聚合。您的查询确实说它将为 $match 进行 IXSCAN,但我不需要 $match - jsut 用于测试添加 {$match: {jobId: {"$exists":"true"}}} 导致执行时间为 8 秒,而不是我已经拥有的 4 秒
    • 要使用覆盖查询,您必须... 我认为$project 在这里更合适 - {$project: {jobId: 1, "result.status": 1}} - 实际上没有帮助...我担心 Mongo 开发人员只是错过了这样的用例。
    猜你喜欢
    • 2010-10-31
    • 2017-03-27
    • 2010-12-02
    • 2017-04-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多