2

我有查询(这很慢~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"
}

当我删除sort({_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"
}

谢谢

4

2 回答 2

7

您的集合中是否确实定义了以下三个索引?

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

地理空间索引文档建议不要在同一个集合上创建多个地理索引。尽管 MongoDB 将允许它,但该行为可能是不可取的。我对您的情况的猜测是,{latlng: '2d'}可能已选择使用非复合索引而不是复合索引。输出在这里explain()并没有真正帮助我们,因为它只是报告GeoBrowse-box而不是索引名称;但是,我建议手动提示光标使用复合索引并查看结果是否有所改善。或者,简单地摆脱非复合索引,{latlng: '2d', _id:-1}因为这是查询优化器的明显且唯一的选择。

最后,{_id: -1}索引是多余的,可以删除。根据复合索引文档,方向仅在处理由多个字段组成的索引时才相关。对于单键索引,我们可以轻松地向后或向前遍历索引。由于 MongoDB 已经{_id: 1}默认为我们创建了一个索引,因此简单地依赖它会更有效。

现在,使用索引:查询的一个警告是,在按非地理条件(_id在您的情况下)排序之前,限制已应用于地理空间查询组件。我相信这意味着,虽然您的结果确实会按 排序_id,但这种排序可能不会考虑匹配范围内的所有文档。这在文档的复合索引位中提到,其中将SERVER-4247引用为待处理的解决方案。


编辑:跟进您的基准

我填充了示例数据,它们是 ±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排序才考虑在内,这不是我根据此处的注释所期望的。然后我稍微调整了查询​​以检查其他一些情况:

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

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

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

无论如何,基准测试缓慢的另一个原因是地理匹配范围非常广。更严格的界限肯定会导致要排序的数据更少,即使该排序没有索引。也就是说,我确实认为SERVER-4247会对您有所帮助,因为它可能会在执行地理匹配之前先处理非地理排序。

于 2012-10-19T18:08:55.300 回答
0

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

db.markers.ensureIndex({latlng: '2d', _id:-1})
于 2012-10-16T07:56:34.993 回答