我有以下查询,有点像反向范围查找:
db.ip_ranges.find({ $and: [{ start_ip_num: { $lte: 1204135028 } }, { end_ip_num: { $gt: 1204135028 } }] })
当仅使用$ lte标识符运行时,查询立即返回。但是当我在同一个查询中使用$ gt和$ lte运行时,它非常慢(以秒为单位)。
start_ip_num和end_ip_num字段都被编入索引。
如何优化此查询?
修改
当我在查询中使用explain()函数时,我得到以下内容:
{
"cursor" : "BtreeCursor start_ip_num_1",
"nscanned" : 452336,
"nscannedObjects" : 452336,
"n" : 1,
"millis" : 2218,
"nYields" : 0,
"nChunkSkips" : 0,
"isMultiKey" : false,
"indexOnly" : false,
"indexBounds" : {
"start_ip_num" : [
[
-1.7976931348623157e+308,
1204135028
]
]
}
}
编辑2
一旦我添加了复合索引,explain()函数将返回以下内容:
{
"cursor" : "BtreeCursor start_ip_num_1_end_ip_num_1",
"nscanned" : 431776,
"nscannedObjects" : 1,
"n" : 1,
"millis" : 3433,
"nYields" : 0,
"nChunkSkips" : 0,
"isMultiKey" : false,
"indexOnly" : false,
"indexBounds" : {
"start_ip_num" : [
[
-1.7976931348623157e+308,
1204135028
]
],
"end_ip_num" : [
[
1204135028,
1.7976931348623157e+308
]
]
}
}
但是,性能仍然很差(几秒钟)。
答案 0 :(得分:5)
根据Ip2location website,可以使用mongodb在没有范围查询的情况下实现对ip地址的快速查询。
在mongodb { ip_to: 1 }
上只创建一个索引,并使用以下命令查询ip:
db.collection_name.find({ ip_to: { $gte : ip_integer } }).sort({ ip_end: 1 }).limit(1)
使用此配置,我获得了1毫秒的查询时间和600万个文档集。
答案 1 :(得分:4)
因此,在Mongo中,双范围查询是不明智的。我假设您有一个包含{start_ip_num: 1, end_ip_num: 1}
的索引。
如果这不能让你足够接近(如果第一个字段返回的数据足够,通常它仍然很慢,因为它必须进行大量的B树扫描),你可以做一个技巧使用2D盒子查询来解决这个问题(一次仅适用于两个范围)。
基本上,你将2D地理索引放在一个包含数组中两个点的字段上,比如[start_ip,end_ip],并给它一个足够高的最小值/最大值,这样它就不会达到极限值。默认情况下只有-180/180。
最后,使用一个边界查询,其范围从min到该框一角的$ lte值,以及该框另一角的gt和max值。有关语法,请参阅http://www.mongodb.org/display/DOCS/Geospatial+Indexing#GeospatialIndexing-BoundsQueries。
它看起来像这样:
db.ip_ranges.find({ip_range:{$within:{$box:[[0, 1204135028], [1204135028, max]]}}});
其中max是你可以拥有的最大ip。
我看了一段时间已经有一段时间了,所以盒子可能是错的,但概念是合理的,并且它使得双范围查询比常规的两个字段B树索引执行得更好。一般在一秒钟内(虽然通常是几百毫秒),与常规索引的几秒钟相比 - 我认为当时我有数亿个文档,但它已经有一段时间了,所以请记住这些记忆基准用谷物盐。根据您的数据和范围大小,结果会有很大差异,我敢肯定。
更新:您可能希望尝试bits
设置,尝试使用较低的数字和较高的数字来查看它是否有所作为。对我来说,它似乎不会影响平均查询。有关语法,请参阅http://www.mongodb.org/display/DOCS/Geospatial+Indexing#GeospatialIndexing-CreatingtheIndex。
答案 2 :(得分:0)
经过大量的实验和研究,我发现了这个:
https://groups.google.com/forum/?fromgroups=#!topic/mongodb-user/IUwOzWsc0Sg
我可以使用此查询将查询大约200-300毫秒,并且删除所有索引(您必须删除所有索引才能使其工作!!! < /强>):
db.ip_ranges.find({start_ip_num:{$ lte:1204135028},end_ip_num:{$ gt:1204135028}})。limit(1)
不要问我为什么。我无法解释。如果您有兴趣,我正在使用MongoDB从MaxMind构建GeoIP数据库。
答案 3 :(得分:0)
诀窍是使用$ lte和sort。我把查询缩短到几毫秒。
我遇到了完全相同的问题 - 找到哪个CIDR块与特定IP地址匹配。我也尝试使用$ gte和$ lte,并且获得了10秒的响应时间。
我以不同的方式解决了这个问题。请注意,MaxMind数据库中的CIDR块(IP地址范围)不会重叠。每个IP地址最多匹配一个结果。因此,您需要做的就是找到最大start_ip_num小于特定IP地址的CIDR块。然后在应用程序代码中验证end_ip_num是否大于特定的IP地址。
这里是代码(使用MongoDB客户端节点):
// Convert IP address to base 10.
var ipToDecimal = function (ipAddress) {
var split = ipAddress.split('.');
return (split[0] * 16777216) + (split[1] * 65536) + (split[2] * 256) + (+split[3]);
};
var ipAddress = '1.2.3.4';
var ipDecimal = ipToDecimal(ipAddress);
db.ip_addresses.find({start_ip_num: {$lte: ipDecimal}}, {_id: 0, country_code: 1, end_ip_num: 1}, {limit: 1, sort: [['start_ip_num', -1]]}).toArray(function (error, ipAddresses) {
if (ipAddresses[0] && ipAddresses[0]['end_ip_num'] >= ipDecimal) {
console.log('IP address found: ', ipAddresses[0]['country_code']);
} else {
console.log('IP address not found.');
}
});
确保在start_ip_num上创建索引。