4

我有大量的文档,例如:

{ loc: [10.32, 24.34], relevance: 0.434 }

并希望能够有效地执行如下查询:

 { "loc": {"$geoWithin":{"$box":[[-103,10.1],[-80.43,30.232]]}} }

与任意框。

添加 2d 索引loc使这非常快速和高效。但是,我现在也想获得最相关的文件:

.sort({ relevance: -1 })

这导致一切都变得爬行(在任何特定的盒子里都可能有大量的结果,我只需要前 10 个左右)。

非常感谢任何建议或帮助!

4

4 回答 4

6

您是否尝试过使用聚合框架?

两阶段管道可能会起作用:

  1. 使用现有 $geoWithin 查询的$match阶段。
  2. 一个$sort阶段,按relevance: -1

这是它可能看起来的示例:

db.foo.aggregate(
    {$match: { "loc": {"$geoWithin":{"$box":[[-103,10.1],[-80.43,30.232]]}} }},
    {$sort: {relevance: -1}}
);

我不确定它会如何表现。然而,即使它在 MongoDB 2.4 中表现不佳,在 2.6/2.5 中也可能会有很大的不同,因为 2.6 将包括改进的聚合排序性能

于 2013-09-03T09:16:43.163 回答
2

当匹配特定框的结果很大时,排序操作非常昂贵,因此您绝对想避免它。尝试在相关字段上创建单独的索引并尝试使用它(根本没有 2d 索引):这样查询将更有效地执行 - 文档(已经按相关性排序)将被逐一扫描,以匹配给定的地理框条件。当找到前 10 名时,你就很好了。

不过,如果地理框只匹配集合的一小部分,它可能不会那么快。在最坏的情况下,它需要扫描整个集合。

我建议您创建 2 个索引(loc 与相关性)并对您的应用程序中常见的查询运行测试(使用 mongo 的提示强制使用所需的索引)。

根据您的测试结果,您甚至可能想要添加一些应用程序逻辑,以便如果您知道该框很大,您可以使用相关索引运行查询,否则使用 loc 2d 索引。只是一个想法。

于 2013-09-02T21:22:30.717 回答
2

当您尝试使用对复合键部分进行排序时,您不能将扫描和订单值设为 0。不幸的是,目前没有解决您的问题的方法,这与您使用 2d 索引的现象无关。

当您在查询上运行解释命令时,“scanAndOrder”的值显示天气在收集结果后需要有一个排序阶段。如果是真的,则需要在查询后进行排序,如果是错误的排序是没有必要。

为了测试这种情况,我以这种方式在示例数据库中创建了一个名为 t2 的集合:

db.createCollection('t2')
db.t2.ensureIndex({a:1})
db.t2.ensureIndex({b:1})
db.t2.ensureIndex({a:1,b:1})
db.t2.ensureIndex({b:1,a:1})

for(var i=0;i++<200;){db.t2.insert({a:i,b:i+2})}

虽然您只能使用 1 个索引来支持查询,但我做了以下测试,结果包括:

mongos> db.t2.find({a:{$gt:50}}).sort({b:1}).hint("b_1").explain()
{
    "cursor" : "BtreeCursor b_1",
    "isMultiKey" : false,
    "n" : 150,
    "nscannedObjects" : 200,
    "nscanned" : 200,
    "nscannedObjectsAllPlans" : 200,
    "nscannedAllPlans" : 200,
    "scanAndOrder" : false,
    "indexOnly" : false,
    "nYields" : 0,
    "nChunkSkips" : 0,
    "millis" : 0,
    "indexBounds" : {
        "b" : [
            [
                {
                    "$minElement" : 1
                },
                {
                    "$maxElement" : 1
                }
            ]
        ]
    },
    "server" : "localhost:27418",
    "millis" : 0
}
mongos> db.t2.find({a:{$gt:50}}).sort({b:1}).hint("a_1_b_1").explain()
{
    "cursor" : "BtreeCursor a_1_b_1",
    "isMultiKey" : false,
    "n" : 150,
    "nscannedObjects" : 150,
    "nscanned" : 150,
    "nscannedObjectsAllPlans" : 150,
    "nscannedAllPlans" : 150,
    "scanAndOrder" : true,
    "indexOnly" : false,
    "nYields" : 0,
    "nChunkSkips" : 0,
    "millis" : 1,
    "indexBounds" : {
        "a" : [
            [
                50,
                1.7976931348623157e+308
            ]
        ],
        "b" : [
            [
                {
                    "$minElement" : 1
                },
                {
                    "$maxElement" : 1
                }
            ]
        ]
    },
    "server" : "localhost:27418",
    "millis" : 1
}
mongos> db.t2.find({a:{$gt:50}}).sort({b:1}).hint("a_1").explain()
{
    "cursor" : "BtreeCursor a_1",
    "isMultiKey" : false,
    "n" : 150,
    "nscannedObjects" : 150,
    "nscanned" : 150,
    "nscannedObjectsAllPlans" : 150,
    "nscannedAllPlans" : 150,
    "scanAndOrder" : true,
    "indexOnly" : false,
    "nYields" : 0,
    "nChunkSkips" : 0,
    "millis" : 1,
    "indexBounds" : {
        "a" : [
            [
                50,
                1.7976931348623157e+308
            ]
        ]
    },
    "server" : "localhost:27418",
    "millis" : 1
}


 mongos> db.t2.find({a:{$gt:50}}).sort({b:1}).hint("b_1_a_1").explain()
{
    "cursor" : "BtreeCursor b_1_a_1",
    "isMultiKey" : false,
    "n" : 150,
    "nscannedObjects" : 150,
    "nscanned" : 198,
    "nscannedObjectsAllPlans" : 150,
    "nscannedAllPlans" : 198,
    "scanAndOrder" : false,
    "indexOnly" : false,
    "nYields" : 0,
    "nChunkSkips" : 0,
    "millis" : 0,
    "indexBounds" : {
        "b" : [
            [
                {
                    "$minElement" : 1
                },
                {
                    "$maxElement" : 1
                }
            ]
        ],
        "a" : [
            [
                50,
                1.7976931348623157e+308
            ]
        ]
    },
    "server" : "localhost:27418",
    "millis" : 0
}

单个字段的索引没有多大帮助,所以 a_1(不支持排序)和 b_1(不支持 queryin)是 out 。a_1_b_1 上的索引也不幸运,虽然它的性能比单个 a_1 差,但 mongoDB 引擎不会利用与存储的一个 'a' 值相关的部分以这种方式排序的情况。值得尝试的是复合索引 b_1_a_1 ,在您的情况下,relevant_1_loc_1 虽然它会以有序的方式返回结果,因此 scanAndOrder 将是错误的,我还没有测试 2d 索引,但我认为它会排除扫描仅基于索引值(这就是为什么在这种情况下的测试中 nscanned 高于 nscannedObjects)。不幸的是,该索引将很大,但仍比文档小。

于 2013-09-03T08:53:53.213 回答
1

如果您需要在框(矩形)内搜索,此解决方案是有效的。

地理空间索引的问题在于您只能将其放在复合索引的前面(至少对于 mongo 3.2 是这样)

所以我想为什么不创建自己的“地理空间”索引呢?我只需要在 Lat, Lgn (X,Y) 上创建一个复合索引,并首先添加排序字段。然后我需要实现在框边界内搜索的逻辑,并专门指示 mongo 使用它(提示)。

翻译成你的问题:

db.collection.createIndex({ "relevance": 1, "loc_x": 1, "loc_y": 1 }, { "background": true } )

逻辑:

db.collection.find({
    "loc_x": { "$gt": -103, "$lt": -80.43 },
    "loc_y": { "$gt": 10.1, "$lt": 30.232 }
}).hint("relevance_1_loc_x_1_loc_y_1") // or whatever name you gave it

如果您需要包容性结果,请使用$gte$lte 。

而且您不需要使用.sort()因为它已经排序,或者如果需要,您可以对相关性进行反向排序。

我遇到的唯一问题是盒子面积很小。寻找小区域比寻找大区域花费更多时间。这就是我为小区域搜索保留地理空间索引的原因。

于 2017-08-10T13:17:45.680 回答