MongoDB查询速度慢

时间:2014-09-26 09:41:32

标签: mongodb

我有一个非常简单的mongo查询,应该使用_id索引。 解释计划看起来不错:

> db.items.find({ deleted_at: null, _id: ObjectId('541fd8016d792e0804820100') }).sort({ positions: 1 }).explain()
{
    "cursor" : "BtreeCursor _id_",
    "isMultiKey" : false,
    "n" : 1,
    "nscannedObjects" : 1,
    "nscanned" : 1,
    "nscannedObjectsAllPlans" : 6,
    "nscannedAllPlans" : 7,
    "scanAndOrder" : true,
    "indexOnly" : false,
    "nYields" : 0,
    "nChunkSkips" : 0,
    "millis" : 0,
    "indexBounds" : {
        "_id" : [
            [
                ObjectId("541fd8016d792e0804820100"),
                ObjectId("541fd8016d792e0804820100")
            ]
        ]
    },
    "server" : "mydbserver:27017",
    "filterSet" : false
}

但是当我执行查询时,它会在100-800ms内执行:

> db.items.find({ deleted_at: null, _id: ObjectId('541fd8016d792e0804820100') }).sort({ positions: 1 })
2014-09-26T12:34:00.279+0300 [conn38926] query mydb.items query: { query: { deleted_at: null, _id: ObjectId('541fd8016d792e0804820100') }, orderby: { positions: 1.0 } } planSummary: IXSCAN { positions: 1 } ntoreturn:0 ntoskip:0 nscanned:70043 nscannedObjects:70043 keyUpdates:0 numYields:1 locks(micros) r:1391012 nreturned:1 reslen:814 761ms

为什么报告nscanned:70043 nscannedObjects:70043以及为什么这么慢?

我在CentOS 6上使用MongoDB 2.6.4。

我尝试修复MongoDB,完全转储/导入,没有帮助。

更新1

> db.items.find({deleted_at:null}).count()
67327
> db.items.find().count()
70043

我没有deleted_at的索引,但我的_id索引。

更新2(2014-09-26 14:57 EET)

_id, deleted_at上添加索引并没有帮助,即使explain也没有使用该索引:(

> db.items.ensureIndex({ _id: 1, deleted_at: 1 }, { unique: true })
> db.items.getIndexes()
[
    {
        "v" : 1,
        "key" : {
            "_id" : 1
        },
        "name" : "_id_",
        "ns" : "mydb.items"
    },
    {
        "v" : 1,
        "unique" : true,
        "key" : {
            "_id" : 1,
            "deleted_at" : 1
        },
        "name" : "_id_1_deleted_at_1",
        "ns" : "mydb.items"
    }
]
> db.items.find({ deleted_at: null, _id: ObjectId('541fd8016d792e0804820100') }).sort({ positions: 1 }).explain()
{
    "cursor" : "BtreeCursor _id_",
    "isMultiKey" : false,
    "n" : 1,
    "nscannedObjects" : 1,
    "nscanned" : 1,
    "nscannedObjectsAllPlans" : 7,
    "nscannedAllPlans" : 8,
    "scanAndOrder" : true,
    "indexOnly" : false,
    "nYields" : 0,
    "nChunkSkips" : 0,
    "millis" : 0,
    "indexBounds" : {
        "_id" : [
            [
                ObjectId("541fd8016d792e0804820100"),
                ObjectId("541fd8016d792e0804820100")
            ]
        ]
    },
    "server" : "myserver:27017",
    "filterSet" : false
}

更新3(2014-09-26 15:03:32 EET)

_id, deleted_at, positions上添加索引有帮助。但是,之前的案例迫使全面收集扫描似乎很奇怪。

> db.items.ensureIndex({ _id: 1, deleted_at: 1, positions: 1 })
> db.items.find({ deleted_at: null, _id: ObjectId('541fd8016d792e0804820100') }).sort({ positions: 1 }).explain()
{
    "cursor" : "BtreeCursor _id_1_deleted_at_1_positions_1",
    "isMultiKey" : false,
    "n" : 1,
    "nscannedObjects" : 1,
    "nscanned" : 1,
    "nscannedObjectsAllPlans" : 3,
    "nscannedAllPlans" : 3,
    "scanAndOrder" : false,
    "indexOnly" : false,
    "nYields" : 0,
    "nChunkSkips" : 0,
    "millis" : 0,
    "indexBounds" : {
        "_id" : [
            [
                ObjectId("541fd8016d792e0804820100"),
                ObjectId("541fd8016d792e0804820100")
            ]
        ],
        "deleted_at" : [
            [
                null,
                null
            ]
        ],
        "positions" : [
            [
                {
                    "$minElement" : 1
                },
                {
                    "$maxElement" : 1
                }
            ]
        ]
    },
    "server" : "myserver:27017",
    "filterSet" : false
}

3 个答案:

答案 0 :(得分:1)

这看起来像个错误。查询计划程序应该选择_id索引,_id索引应该是您所需要的,因为它必须立即将结果集减少到一个文档。排序应该是无关紧要的,因为它排序一个文档。这是一个奇怪的案例,因为您明确要求一个_id匹配的文档,然后对其进行排序。你应该可以绕过mongoid并将其作为解决方法。

.explain()不会忽略排序。您可以像这样测试:

> for (var i = 0; i < 100; i++) { db.sort_test.insert({ "i" : i }) }
> db.sort_test.ensureIndex({ "i" : 1 })
> db.sort_test.find().sort({ "i" : 1 }).explain()

如果MongoDB不能使用索引进行排序,它将在内存中排序。 explain输出中的字段scanAndOrder告诉您MongoDB是否不能使用索引对查询结果进行排序(即scanAndOrder : false表示MongoDB可以使用索引对查询结果进行排序)。

您能否在MongoDB SERVER project中提交错误报告?也许工程师会说它按照设计工作但行为看起来不对我,并且已经有2.6.4中的几个查询规划器了。如果之前说过,我可能错过了它,但deleted_at : null的存在/不存在会影响问题吗?

此外,如果您确实提交了一张票,请在您的问题中发布一个链接或作为对该答案的评论,以便其他人可以轻松跟踪。谢谢!

答案 1 :(得分:-1)

UPDATE: 更正了我建议使用(_id,deleted_at)复合索引的答案。在评论中,更清楚说明explain()可能无法反映查询规划器某些情况。

我们期望find()将过滤结果,然后sort()将应用于过滤后的集合。但是,根据this doc,查询规划器将使用_id上的索引,以及postion上此索引的索引(如果有) 。现在,如果您在(_id, position)上有一个复合索引,它应该能够使用该索引来处理查询。

要点是,如果您的查询具有covers的索引,则可以确保查询计划程序使用您的索引。在您的情况下,查询绝对没有涵盖,如解释计划中的indexOnly : false所示。

如果这是设计的,它绝对是违反直觉的,并且wdberkely suggested,你应该提交一份错误报告,以便社区更详细地解释这种奇特的行为。

答案 2 :(得分:-2)

我猜你有70043个ID为'541fd8016d792e0804820100'的对象。你能简单地找一个id并计算()吗?索引并不意味着“唯一”索引 - 如果您有一个具有特定值的索引“桶”,一旦达到存储桶,它现在会在存储桶中进行扫描以查看每个'deleted_at'值以查看其是否为'空值'。要解决此问题,请使用复合索引(id,deleted_at)。