我有一个非常简单的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
}
答案 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)。