12

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

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

4 回答 4

8

因此,在 Mongo 中不建议使用双范围查询。我假设您有一个包含两者的索引{start_ip_num: 1, end_ip_num: 1}

如果这不能让你足够接近(如果你有足够的数据从第一个字段返回,通常它仍然很慢,因为它必须进行大量 B-tree 扫描),你可以采取一个技巧来解决这个问题使用 2D 框查询(一次仅适用于两个范围)。

基本上,您在包含数组中两个点的字段上放置一个 2D 地理索引,例如 [start_ip, end_ip],并给它一个足够高的最小值/最大值,这样它就不会达到默认情况下的限制-180/180。

最后,使用边界查询,范围从框的一个角上的 min 到 $lte 值,以及框的另一个角上的 gt 和最大值。有关语法,请参见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

于 2012-10-24T20:56:42.943 回答
7

根据Ip2location 网站,可以在没有范围查询的情况下使用 mongodb 实现对 IP 地址的快速查询。在 mongodb 上只创建一个索引 { ip_to: 1 },并使用以下命令查询 ip:

db.collection_name.find({ ip_to: { $gte : ip_integer } }).sort({ ip_end: 1 }).limit(1)

使用此配置,我获得了 1 毫秒的查询时间和 600 万个文档集合。

于 2016-01-07T18:11:48.267 回答
1

诀窍是使用 $lte 和排序。我将查询缩短到几毫秒。

我遇到了完全相同的问题 - 查找与特定 IP 地址匹配的 CIDR 块。我还尝试使用 $gte 和 $lte 并获得 10 秒的响应时间。

我以不同的方式解决了这个问题。请注意,MaxMind 数据库中的 CIDR 块(IP 地址范围)不重叠。每个 IP 地址最多匹配一个结果。因此,您需要做的就是找到具有小于特定 IP 地址的最大 start_ip_num 的 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 上创建索引。

于 2015-04-17T07:06:19.363 回答
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 数据库。

于 2012-10-25T06:07:16.687 回答