有效地对mongodb地理空间查询的结果进行排序

时间:2013-08-28 16:43:45

标签: mongodb

我有很多文档集合,如:

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

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

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

任意方框。

loc上添加2d索引可以非常快速有效。但是,我现在也想获得最相关的文件:

.sort({ relevance: -1 })

这会导致所有内容都被抓取(在任何特定的框中都会有大量的结果,而我只需要前10名左右)。

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

4 个答案:

答案 0 :(得分:6)

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

两阶段管道可能有效:

  1. 使用现有$ geoWithin查询的 $ match 阶段。
  2. relevance: -1
  3. 排序的 $ sort 阶段

    以下是它的外观示例:

    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将包含improved aggregation sort performance

答案 1 :(得分:2)

如果有一个巨大的结果匹配特定的盒子,排序操作真的很贵,所以你一定要避免它。 尝试在相关字段上创建单独的索引并尝试使用它(根本没有2d索引):查询将以这种方式更有效地执行 - 文档(已经按相关性排序)将逐一扫描匹配给定的地理位置条件。当前10名被发现时,你很好。

但是,如果geo box只匹配集合的小子集,那可能不会那么快。在最坏的情况下,它需要扫描整个集合。

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

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

答案 2 :(得分:2)

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

当您对查询运行explain命令时,“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)已经完成。 a_1_b_1上的索引也不幸运,而它将比单个a_1表现更差,mongoDB引擎将不会利用与存储的一个'a'值相关的部分以这种方式排序的情况。值得尝试的是复合索引b_1_a_1,在您的情况下为1/2 [loc_1],而它将以有序方式返回结果,因此scanAndOrder将为false并且我没有测试2d索引但我认为它将排除扫描一些文档仅基于索引值(这就是为什么在测试中,nscanned高于nscannedObjects)。不幸的是,该指数将是巨大的,但仍然比文档小。

答案 3 :(得分:1)

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

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

所以我想为什么不创建我自己的&#34;地理空间&#34;指数?我只需要在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(),因为它已经排序,或者您可以对相关性进行反向排序你需要。

我遇到的唯一问题是盒子区域很小。找到小区域而不是大区域需要更多时间。这就是我为小区域搜索保留地理空间索引的原因。