如何使用$ gt和$ lte优化MongoDB查询?

时间:2012-10-24 20:40:42

标签: mongodb mongodb-query

我有以下查询,有点像反向范围查找:

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
            ]
        ]
    }
}

但是,性能仍然很差(几秒钟)。

4 个答案:

答案 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上创建索引。