具有排序性能问题的MongoDB地理空间查询

时间:2012-10-16 06:40:04

标签: performance mongodb indexing schema geospatial

我有查询(非常慢 ~2,5s ):

db.markers.find({ latlng: { '$within': { '$box': [ [ -16, -140 ], [ 75, 140 ] ] } } }).sort({_id: -1}).limit(1000)

当我为这个查询运行解释时,我得到了

{
   "cursor" : "GeoBrowse-box",
   "isMultiKey" : false,
   "n" : 1000,
   "nscannedObjects" : 242331,
   "nscanned" : 242331,
   "nscannedObjectsAllPlans" : 242331,
   "nscannedAllPlans" : 242331,
   "scanAndOrder" : true,
   "indexOnly" : false,
   "nYields" : 1383,
    "nChunkSkips" : 0,
    "millis" : 2351,
    "indexBounds" : {
        "latlng" : [ ]
    },
    "lookedAt" : NumberLong(262221),
    "matchesPerfd" : NumberLong(242331),
    "objectsLoaded" : NumberLong(242331),
    "pointsLoaded" : NumberLong(0),
    "pointsSavedForYield" : NumberLong(0),
    "pointsChangedOnYield" : NumberLong(0),
    "pointsRemovedOnYield" : NumberLong(0),
    "server" : "xx:27017"
}

当我删除排序({_ id:-1})时,解释会给我(快速查询 5 milis ):

{
    "cursor" : "GeoBrowse-box",
    "isMultiKey" : false,
    "n" : 1000,
    "nscannedObjects" : 1000,
    "nscanned" : 1000,
    "nscannedObjectsAllPlans" : 1000,
    "nscannedAllPlans" : 1000,
    "scanAndOrder" : false,
    "indexOnly" : false,
    "nYields" : 0,
    "nChunkSkips" : 0,
    "millis" : 5,
    "indexBounds" : {
        "latlng" : [ ]
    },
    "lookedAt" : NumberLong(1000),
    "matchesPerfd" : NumberLong(1000),
    "objectsLoaded" : NumberLong(1000),
    "pointsLoaded" : NumberLong(0),
    "pointsSavedForYield" : NumberLong(0),
    "pointsChangedOnYield" : NumberLong(0),
    "pointsRemovedOnYield" : NumberLong(0),
        "server" : "xx:27017"
}

我在latlng上有2d索引,在_id和复合索引上有desc索引。

db.markers.ensureIndex({latlng: '2d', _id:-1})
db.markers.ensureIndex({ latlng: '2d' })
db.markers.ensureIndex({ _id: -1 })

我想要实现的是从最新的特定区域获取标记。

任何想法或建议如何做得少于 2.5秒 ??

如果有人想做自己的测试

var i = 0,
  lat = 0,
  lng = 0;

for (i; i < 260000; i++) {
  lat = parseFloat(Math.min(-90 + (Math.random() * 180), 90).toFixed(6));
  lng = parseFloat(Math.min(-180 + (Math.random() * 360), 180).toFixed(6));
  collection.insert({latlng: [lat, lng]}, function () {});
}

collection.find({ latlng: { '$within': { '$box': [ [ -90, -180 ], [ 90, 180 ] ] } } }, {latlng: 1, _id: 1 }).sort({_id: -1}).limit(1000).explain()

在我的本地机器上收到( ~2,6s ):

{
    "cursor" : "GeoBrowse-box",
    "isMultiKey" : false,
    "n" : 1000,
    "nscannedObjects" : 260000,
    "nscanned" : 260000,
    "nscannedObjectsAllPlans" : 260000,
    "nscannedAllPlans" : 260000,
    "scanAndOrder" : true,
    "indexOnly" : false,
    "nYields" : 1612,
    "nChunkSkips" : 0,
    "millis" : 2613,
    "indexBounds" : {
            "latlng" : [ ]
    },
    "lookedAt" : NumberLong(260000),
    "matchesPerfd" : NumberLong(260000),
    "objectsLoaded" : NumberLong(260000),
    "pointsLoaded" : NumberLong(0),
    "pointsSavedForYield" : NumberLong(0),
    "pointsChangedOnYield" : NumberLong(0),
    "pointsRemovedOnYield" : NumberLong(0),
    "server" : "xx:27017"
}

THX

2 个答案:

答案 0 :(得分:7)

您确实在集合中定义了以下三个索引吗?

db.markers.ensureIndex({ latlng: '2d', _id:-1 })
db.markers.ensureIndex({ latlng: '2d' })
db.markers.ensureIndex({ _id: -1 })

geospatial indexing文档建议不要在同一个集合上创建多个地理索引。虽然MongoDB会允许它,但这种行为可能是不受欢迎的。我对你的案例的猜测是,可能已经选择使用非复合{latlng: '2d'}而不是复合索引。 explain()输出在这里并没有真正帮助我们,因为它只是报告GeoBrowse-box而不是索引名称;但是,我建议手动hinting光标使用复合索引并查看结果是否有所改善。或者,简单地删除非复合索引,因此{latlng: '2d', _id:-1}因为查询优化器的明显且唯一的选择。

最后,{_id: -1}索引是多余的,可以删除。根据{{​​3}}文档,方向仅在处理由多个字段组成的索引时才有意义。对于单键索引,我们可以轻松地向后或向前走索引。由于MongoDB默认为我们创建了{_id: 1}索引,因此单纯依赖它就更有效。

现在,将索引排除在外:对您的查询的一个警告是,在按非地理标准(在您的情况下为_id)排序之前,会对地理空间查询组件应用限制。我相信这意味着,虽然您的结果确实会按_id排序,但这种排序可能不会考虑匹配范围内的所有文档。这在文档的compound index位中提到,它将compound index引用为待定解决方案。


修改:跟进您的基准

我填充了示例数据,它们是在±90和±180之间的260k随机点。然后我运行了你的查询:

db.markers.find(
  { latlng: { $within: { $box: [[-90, -180], [90, 180]] }}},
  { latlng: 1, _id: 1 }
).sort({_id: -1}).limit(1000).explain()

花了1713毫秒(我将它用作比较的基线而不是你的2351毫秒的时间)。我还会注意到查询匹配了所有260k文档,并扫描了相同数量的索引条目。看起来这个限制在_id排序之前没有考虑因素,这不是我根据笔记SERVER-4247所预期的。然后我稍微调整了一下查询以检查其他一些情况:

  • 没有_id排序和限制的原始查询:nscanned为260k,时间为1470毫秒。
  • 没有_id排序的原始查询:nscanned为1000,时间为9毫秒。
  • 没有限制的原始查询:nscanned为260k,时间为2567ms。

我还想单独在未编制索引的字段上测试排序,以模拟地理匹配后_id排序可能发生的情况;但是,我无法使用_id,因为默认索引将始终存在。为此,我删除了复合地理索引,然后按latlng对象排序。这导致nscanned为260k,时间为1039ms。如果我添加1000的限制,时间是461ms。

如果我们将其添加到上面的1470毫秒(没有排序和限制的地理查询),它非常接近原始查询而没有限制,即2567毫秒。同样,如果我们将461毫秒(here)添加到1470毫秒,它接近1713毫秒的原始基准测试结果。基于这种相关性,我打赌你基准测试中的_id排序根本没有利用复合指数。

无论如何,基准测试速度缓慢的另一个原因是由于地理匹配非常广泛。更严格的界限肯定会导致更少的数据进行排序,即使这种排序没有索引。也就是说,我认为limited sort会对您有所帮助,因为它可能会在执行地理位置匹配之前先处理非地理排序。

答案 1 :(得分:0)

您的索引是否使用复合键?

db.markers.ensureIndex({latlng: '2d', _id:-1})