【发布时间】:2013-09-16 13:07:28
【问题描述】:
我有一个 GeoJSON Point 形式的坐标数据集合,我需要从中查询一个区域内的 10 个最新条目。现在有 1.000.000 个条目,但将增加大约 10 倍。
我的问题是,当所需区域内有很多条目时,我的查询性能会大大下降(案例 3)。我目前拥有的测试数据是随机的,但真实数据不会,因此无法仅根据区域的尺寸选择另一个索引(如案例 4)。
我应该怎么做才能让它无论在哪个区域都能按预期执行?
1.收集统计:
> db.randomcoordinates.stats()
{
"ns" : "test.randomcoordinates",
"count" : 1000000,
"size" : 224000000,
"avgObjSize" : 224,
"storageSize" : 315006976,
"numExtents" : 15,
"nindexes" : 3,
"lastExtentSize" : 84426752,
"paddingFactor" : 1,
"systemFlags" : 0,
"userFlags" : 0,
"totalIndexSize" : 120416128,
"indexSizes" : {
"_id_" : 32458720,
"position_2dsphere_timestamp_-1" : 55629504,
"timestamp_-1" : 32327904
},
"ok" : 1
}
2。索引:
> db.randomcoordinates.getIndexes()
[
{
"v" : 1,
"key" : {
"_id" : 1
},
"ns" : "test.randomcoordinates",
"name" : "_id_"
},
{
"v" : 1,
"key" : {
"position" : "2dsphere",
"timestamp" : -1
},
"ns" : "test.randomcoordinates",
"name" : "position_2dsphere_timestamp_-1"
},
{
"v" : 1,
"key" : {
"timestamp" : -1
},
"ns" : "test.randomcoordinates",
"name" : "timestamp_-1"
}
]
3.使用 2dsphere 复合索引查找:
> db.randomcoordinates.find({position: {$geoWithin: {$geometry: {type: "Polygon", coordinates: [[[1, 1], [1, 90], [180, 90], [180, 1], [1, 1]]]}}}}).sort({timestamp: -1}).limit(10).hint("position_2dsphere_timestamp_-1").explain()
{
"cursor" : "S2Cursor",
"isMultiKey" : true,
"n" : 10,
"nscannedObjects" : 116775,
"nscanned" : 283424,
"nscannedObjectsAllPlans" : 116775,
"nscannedAllPlans" : 283424,
"scanAndOrder" : true,
"indexOnly" : false,
"nYields" : 4,
"nChunkSkips" : 0,
"millis" : 3876,
"indexBounds" : {
},
"nscanned" : 283424,
"matchTested" : NumberLong(166649),
"geoTested" : NumberLong(166649),
"cellsInCover" : NumberLong(14),
"server" : "chan:27017"
}
4.使用时间戳索引查找:
> db.randomcoordinates.find({position: {$geoWithin: {$geometry: {type: "Polygon", coordinates: [[[1, 1], [1, 90], [180, 90], [180, 1], [1, 1]]]}}}}).sort({timestamp: -1}).limit(10).hint("timestamp_-1").explain()
{
"cursor" : "BtreeCursor timestamp_-1",
"isMultiKey" : false,
"n" : 10,
"nscannedObjects" : 63,
"nscanned" : 63,
"nscannedObjectsAllPlans" : 63,
"nscannedAllPlans" : 63,
"scanAndOrder" : false,
"indexOnly" : false,
"nYields" : 0,
"nChunkSkips" : 0,
"millis" : 0,
"indexBounds" : {
"timestamp" : [
[
{
"$maxElement" : 1
},
{
"$minElement" : 1
}
]
]
},
"server" : "chan:27017"
}
有人建议使用{timestamp: -1, position: "2dsphere"} 索引,所以我也尝试了,但似乎效果不够好。
5.使用 Timestamp + 2dsphere 复合索引查找
> db.randomcoordinates.find({position: {$geoWithin: {$geometry: {type: "Polygon", coordinates: [[[1, 1], [1, 90], [180, 90], [180, 1], [1, 1]]]}}}}).sort({timestamp: -1}).limit(10).hint("timestamp_-1_position_2dsphere").explain()
{
"cursor" : "S2Cursor",
"isMultiKey" : true,
"n" : 10,
"nscannedObjects" : 116953,
"nscanned" : 286513,
"nscannedObjectsAllPlans" : 116953,
"nscannedAllPlans" : 286513,
"scanAndOrder" : true,
"indexOnly" : false,
"nYields" : 4,
"nChunkSkips" : 0,
"millis" : 4597,
"indexBounds" : {
},
"nscanned" : 286513,
"matchTested" : NumberLong(169560),
"geoTested" : NumberLong(169560),
"cellsInCover" : NumberLong(14),
"server" : "chan:27017"
}
【问题讨论】:
-
您能否澄清一下您所说的“因此不可能仅根据区域的尺寸选择另一个索引(如案例 4)。”?在我看来,无论区域大小如何,由于您只寻找最近的十个点,因此您总是会在时间戳索引中做得更好,其中 scanAndOrder 为假且 nscanned 最接近 n。鉴于此,我建议创建一个带有时间戳第一和位置第二的复合索引,但是,当前的 mongo 版本(2.4.6)不会以所需的方式使用它:jira.mongodb.org/browse/SERVER-10801。
-
该区域很大,而且集合中的坐标是随机的,说明有很多,所以时间戳索引更有效。当区域较小且条目较少时,使用时间戳索引时,当该区域少于10个时,需要遍历所有条目。在这种情况下,位置-时间戳复合索引显然是最快的,返回时间为 2 毫秒,而时间戳-位置索引将花费超过 2000 毫秒。我认为我需要(至少)针对不同类型的区域使用不同的索引。
-
我认为您的评论成功了。如果您要在相当大的区域中查询最近的 10 个点,那么问题就从“查找一个区域中的所有点,然后找到最近的 10 个”到“遍历最近的条目并检查它们是否在该地区。”正如你所说,如果所有点的很大一部分都在多边形中,那么第二个会快得多。
-
出于好奇,您能否在没有提示的情况下运行几次此类查询,然后对其运行解释以查看查询优化器使用的索引是什么? MongoDB 旨在为您的查询测试和选择最佳索引,因此您不必考虑这类事情(该功能可能会更好,但它在大多数情况下仍然有效)。
-
使用初始索引时,它会选择 2dsphere-timestamp 复合索引,当我尝试在相当大的区域上运行它几次而没有提示时。所以它没有选择最佳的时间戳索引。
标签: mongodb geospatial