我有很多文档集合,如:
{ loc: [10.32, 24.34], relevance: 0.434 }
并希望能够有效地执行以下查询:
{ "loc": {"$geoWithin":{"$box":[[-103,10.1],[-80.43,30.232]]}} }
任意方框。
在loc
上添加2d索引可以非常快速有效。但是,我现在也想获得最相关的文件:
.sort({ relevance: -1 })
这会导致所有内容都被抓取(在任何特定的框中都会有大量的结果,而我只需要前10名左右)。
任何建议或帮助都非常感谢!!
答案 0 :(得分:6)
您是否尝试过使用聚合框架?
两阶段管道可能有效:
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将包含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(),因为它已经排序,或者您可以对相关性进行反向排序你需要。
我遇到的唯一问题是盒子区域很小。找到小区域而不是大区域需要更多时间。这就是我为小区域搜索保留地理空间索引的原因。