查询在没有索引的情况下执行得更快

Query performing faster without the index

以下是我数据库中文档的简化版本:

{
    _id : 1,
    main_data : 100,
    sub_docs: [
        {
            _id : a,
            data : 22
        },
        {
            _id: b,
            data : 859
        },
        {
            _id: c,
            data: 151
        },

        ... snip ...

        {
           _id: m,
           data: 721
        },
        {
           _id: n,
           data: 111
        }
    ]
}

所以想象一下,我有一百万个具有不同数据值(比如 0 - 1000)的文档。目前我的查询是这样的:

db.myDb.find(
    { sub_docs: { $elemMatch: { data: { $gte: 110, $lt: 160 } } } }
)

另外说上面的查询只会匹配大约 0.001% 的数据(因此总共返回大约 10 个文档)。

我有一个索引集使用:

db.myDb.ensureIndex( sub_docs.data )

对该数据执行定时测试似乎表明它在 sub_docs.data 上没有设置任何索引的情况下速度更快。

我正在使用 Mongo 3.2.8.

编辑 - 附加信息:

我的定时测试是一个Perl脚本,查询服务器然后拉回相关数据。我 运行 在启用索引时首先进行了此测试,但是缓慢的查询时间迫使我进行了一些挖掘。我想看看如果我删除索引,查询时间会有多糟糕,但是它提高了查询的响应时间! 我走得更远,我绘制了查询响应时间与数据库中文档总数的关系图,两个图都显示查询时间呈线性增加,但查询 和索引 增加了速度快得多。 通过测试,我一直在关注服务器内存使用情况(低),因为我的第一个想法是索引不适合内存。

总的来说,我的问题是:为什么对于这个特定的查询,这个查询在没有索引的情况下执行得更好? 无论如何,有没有更好的索引来提高这个查询的速度?

更新

好的,已经有一段时间了,我已经将范围缩小到不限制查询搜索参数两侧的索引。

上面的查询将显示索引范围:

[-inf, 160]

而不是 110 到 160。 我可以通过使用索引最小值和最大值函数来解决这个问题,如下所示:

db.myDb.find(
    { sub_docs: { $elemMatch: { data: { $gte: 110, $lt: 160 } } } }
).min({'subdocs.data': 110}).max({'subdocs.data': 160})

但是(如果可能的话)我更喜欢用不同的方式来做这件事,因为我想使用聚合函数(它似乎不支持 min/max 索引函数)

好的,最后我设法对它进行了排序。无论出于何种原因,索引都没有像我预期的那样限制查询。

运行这个:

db.myDb.find({ sub_docs: { $elemMatch: { data: { $gte: 110, $lt: 160 } } } }).explain()

索引正在执行的操作的片段如下:

                      "inputStage" : {
                                "stage" : "IXSCAN",
                                "keyPattern" : {
                                        "sub_docs.data" : 1
                                },
                                "indexName" : "sub_docs.data_1",
                                "isMultiKey" : true,
                                "isUnique" : false,
                                "isSparse" : false,
                                "isPartial" : false,
                                "indexVersion" : 1,
                                "direction" : "forward",
                                "indexBounds" : {
                                        "sub_docs.data" : [
                                                "[-inf.0, 160.0)"
                                        ]
                                }
                        }

它没有将索引限制在 110 到 160 之间,而是扫描所有与小于或等于 160 的索引键匹配的文档。 我没有包括它,但另一个被拒绝的计划是 110 到 inf+ 的索引扫描。 您可以使用我在评论中上面提到的 min/max 限制来解决此问题,但这意味着您不能使用聚合框架,这很糟糕。

所以我找到的解决方案是将我想要索引的所有数据提取到一个数组中:

{
    _id : 1,
    main_data : 100,
    index_values : [
        22,
        859,
        151,

      ...snip...

        721,
        111
    ],
    sub_docs: [
        {
            _id : a,
            data : 22
        },
        {
            _id: b,
            data : 859
        },
        {
            _id: c,
            data: 151
        },

        ... snip ...

        {
           _id: m,
           data: 721
        },
        {
           _id: n,
           data: 111
        }
    ]
}

然后我创建索引:

db.myDb.ensureIndex({index_values : 1})

然后查询:

db.myDb.find({ index_values : { $elemMatch: { $gte: 110, $lt: 160 } } }).explain()

产生:

"indexBounds" : {
       "index_values" : [
           "[110.0, 160.0]"
       ]
}

现在要检查的文件少了很多!