即使我将字段编入索引,MongoDB查询也会超时

时间:2018-03-06 12:58:17

标签: mongodb mongoose

我正在对一个非常大的集合(500米文档)运行查询,有时会超时(6分钟)或需要很长时间(3-6分钟)。

我索引了所有相关字段(没有复合索引):Tag,trophies,battleLogMonitorFrequency,profileRefreshedAt被索引,totalIndexSize低于我的内存的60%(45gb索引大小为153gb,可用于mongodb)。

    {
            "v" : 2,
            "key" : {
                    "battleLogMonitorFrequency" : 1,
                    "profileRefreshedAt" : 1,
                    "trophies" : -1
            },
            "name" : "battleLogMonitorFrequency_1_profileRefreshedAt_1_trophies_-1",
            "ns" : "dbname.playerprofiles",
            "background" : true
    }

我的问题:

为什么在获得排序结果之前需要这么长时间(甚至超时)?我认为索引我排序和过滤的字段对于该查询应该足够了吗?

修改:完整查询说明结果:https://hastebin.com/ofixobasix.bash

编辑2: getIndexes()的输出:https://hastebin.com/azayojokez.scala

编辑3:在我的查询使用复合索引的建议之后,我注意到结果根本没有改变。查询仍然需要很长时间才能执行。请参阅以下说明结果:https://hastebin.com/ragixuqaci.bash

已添加此索引:

file.getName()

2 个答案:

答案 0 :(得分:3)

您的下载方式如下:

您创建了复合索引{battleLogMonitorFrequency: 1, profileRefreshedAt: 1, trophies: -1},并在排序期间遇到内存不足问题。

errmsg: \"Sort operation used more than the maximum 33554432 bytes of RAM. Add an index, or specify a smaller limit.\""
  

我按照以下步骤解决了这个问题。

1。创建了100m记录的集合

db.myc.count()  > 100034080

我的查询如下:

db.myc.find({field1  : 1, field2: {$lt : 800}}).sort({field3 : 1})

查询应该返回 38.9m记录(我知道这很大但我想加载测试)

<强> 2。然后我创建了索引{field1 : 1, field2: 1, field3:1}

执行查询并获得Out of Memory for Sort。在这里,我能够重现OP的问题。

(解释片段)

"executionStats" : {
        "executionSuccess" : false,
        "errorMessage" : "Exec error resulting in state FAILURE :: caused by :: errmsg: \"Sort operation used more than the maximum 33554432 bytes of RAM. Add an index, or specify a smaller limit.\"",
        "errorCode" : 96,
        "nReturned" : 0,
        "executionTimeMillis" : 19033,
        "totalKeysExamined" : 322639,
        "totalDocsExamined" : 322639,
        "executionStages" : {

            "inputStage" : {

                "inputStage" : {

                    "inputStage" : {

                        "indexName" : "field1_1_field2_1_field3_1",
                        "isMultiKey" : false,
                        "multiKeyPaths" : {
                            "field1" : [ ],
                            "field2" : [ ],
                            "field3" : [ ]
                        },

                    }
                }
            }
        }
    }

第3。 (解决方案)更改了索引{field1 : 1, field3: 1, field2:1} 中字段的顺序 重新执行查询,这次我得到了我的回复。 此外,totalDocsExaminednReturned实际上相同,这表明Mongo查询优化工具完美地使用了索引。

"executionStats" : {
        "executionSuccess" : true,
        "nReturned" : 38901493,
        "executionTimeMillis" : 1571781,
        "totalKeysExamined" : 38902394,
        "totalDocsExamined" : 38901493,
        "executionStages" : {

            "inputStage" : {

                "indexName" : "field1_1_field3_1_field2_1",
                "isMultiKey" : false,
                "multiKeyPaths" : {
                    "field1" : [ ],
                    "field3" : [ ],
                    "field2" : [ ]
                },

            }
        }
    }

虽然,我的查询花了很长时间才能执行,但这很明显,因为它返回(不切实际的)38.9m记录。我更关心的是mongo使用正确的索引吗?答案是肯定的。

<强>说明: OP的查询是Mongo Equality, Range, Sort问题的典型问卷。 当索引超过equality field - range field - sort field Mongo时,仅将其用于过滤器而不用于排序。因此,排序在内存中执行。 为了解决这个问题,我们需要将范围字段保留在复合索引的末尾。

我找到了good article来更好地解释这个情景。

答案 1 :(得分:1)

根据explain()输出,mongod选择的索引为profileRefreshedAt_1,并且需要扫描408295390个键。这是整个系列的约82%。然后,这个大的结果集需要非索引匹配来满足battleLogMonitorFrequency子句。这意味着,408295390返回的每个IXSCAN密钥需要由mongod匹配,无需索引,将每个文档从磁盘拉出,进入缓存,以进行评估

查看集合中可用的索引,我建议在battleLogMonitorFrequencyprofileRefreshedAt上创建复合索引。根据哪个字段的查询匹配较少,这应该在索引中排在第一位。例如,给定以下条件:

  • battleLogMonitorFrequency $eq 336有100,000场比赛
  • profileRefreshedAt $lt新日期(“2018-04-29T00:00:00.000Z”)有100,000,000场比赛

我们想要创建表单的索引 battleLogMonitorFrequency:1, profileRefreshedAt:1因为battleLogMonitorFrequency上的查询首先减少了我们在profileRefreshedAt子句中扫描的密钥数量。

如果条件是相反的,那么我们将反转索引中的键排序。有关更多详细信息,请参阅https://docs.mongodb.com/manual/tutorial/create-queries-that-ensure-selectivity/index.html