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

如何同时使用$gt和$lte优化MongoDB查询?,mongodb,mongodb-query,Mongodb,Mongodb Query,我有一个类似于反向范围查找的查询: db.ip_ranges.find({ $and: [{ start_ip_num: { $lte: 1204135028 } }, { end_ip_num: { $gt: 1204135028 } }] }) 当仅使用$lte标识符运行时,查询将立即返回。但当我在同一个查询中同时运行$gt和$lte时,速度非常慢(以秒为单位) “开始ip数”和“结束ip数”字段都已编制索引 如何优化此查询 编辑 在查询中使用explain()函数时,我得到以下结果: {

我有一个类似于反向范围查找的查询:

db.ip_ranges.find({ $and: [{ start_ip_num: { $lte: 1204135028 } }, { end_ip_num: { $gt: 1204135028 } }] })
当仅使用$lte标识符运行时,查询将立即返回。但当我在同一个查询中同时运行$gt和$lte时,速度非常慢(以秒为单位)

“开始ip数”和“结束ip数”字段都已编制索引

如何优化此查询

编辑

在查询中使用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
            ]
        ]
    }
}

但是,性能仍然很差(以秒为单位)。

因此,在Mongo中,双范围查询是不明智的。我假设您有一个包含
{start\u ip\u num:1,end\u ip\u num:1}
的索引

如果这还不能使您足够接近(如果第一个字段返回的数据足够多,通常速度仍然很慢,因为它必须进行大量的B-树扫描),那么可以使用2D框查询(一次仅适用于两个范围)来解决这一问题

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

最后,使用边界查询,范围从最小值到框的一角的$lte值,以及框的另一角的gt和最大值。有关语法,请参见

它看起来像这样:

db.ip_ranges.find({ip_range:{$within:{$box:[[0, 1204135028], [1204135028, max]]}}});
其中max是您可以拥有的最大ip

我已经有一段时间没有考虑过这个问题了,所以这个框可能是错误的,但是这个概念是正确的,它使得双范围查询的性能比常规的两字段B树索引要好一些。与常规索引的几秒钟相比,始终低于一秒钟(虽然通常为几百毫秒)——我想当时我有数亿个文档,但已经有一段时间了,所以对这些记忆中的基准测试持保留态度。我敢肯定,根据您的数据和范围大小,结果会有很大差异


更新:您可能想要尝试
位设置,尝试一个低的数字和一个高的数字,看看它是否有区别。对我来说,平均而言,这似乎不会影响查询。有关语法,请参阅。

经过大量的实验和研究,我发现:

使用此查询,我可以将查询速度降低约200-300毫秒,删除所有索引(您必须删除所有索引才能正常工作!!!):

db.ip_ranges.find({start_ip_num:{$lte:1204135028},end_ip_num:{$gt:1204135028})。限制(1)


别问我为什么。我无法解释。如果您感兴趣,我正在使用MongoDB从MaxMind构建GeoIP数据库。

诀窍是使用$lte和排序。我把问题归结到几位女士身上

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

我用另一种方法解决了这个问题。请注意,MaxMind数据库中的CIDR块(IP地址范围)不重叠。每个IP地址最多匹配一个结果。因此,您所需要做的就是找到具有最大start_ip_num(小于特定ip地址)的CIDR块。然后在应用程序代码中验证end_ip_num是否大于特定ip地址

以下是代码(使用节点MongoDB客户端):

确保在start_ip_num上创建索引。

根据,可以使用mongodb实现对ip地址的快速查询,而无需范围查询。 仅在mongodb
{ip_to:1}
上创建一个索引,并使用以下内容查询ip:

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

通过这个配置,我在600万个文档集合中获得了1ms的查询时间。

a
.find({…}).explain()
是一个很好的起点。正如Wes Freeman所问的,您是否在
{start\u ip\u nm:1,end\u ip\u num:1}
上有索引?您应该解决的一个问题是使用单个查询选择器对象,而不是使用
$和
db.ip_ranges.find({start_ip_num:{$lte:1204135028},end_ip_num:{$gt:1204135028})
B树必须扫描>400k个条目才能找到一个匹配项。尝试框查询,看看这是否有帮助。我打赌你会在一秒钟内得到它。是的,我已经在start_ip_num和end_ip_num字段上有两个单独的索引。。。让我试试你的解决方案。。。谢谢首先在两个字段上尝试使用复合索引。记住,mongo每个查询只能使用一个索引。在问题中发布explain()结果——这可能会说明问题。我已经尝试了$box查询。它起作用,大约1秒钟后返回。这太奇怪了。我只有大约一百万份文件。看起来像这样的远程操作应该是相当直接的,但Mongo根本没有处理好它。这不会变得更奇怪。当我删除所有索引时,查询返回的速度更快(800毫秒)。我在一篇文章中发现了与我相同的问题。必须在没有索引的情况下执行最快的查询:(你知道为什么吗???Mongo不能很好地进行双范围查询。你的无索引结果在规模上不会保持很快,但如果这只是一个GeoIP列表,它可能不会增长太多。此外,你从未提到你只需要一个结果。你可以只做findOne而不是.limit(1)我很好奇为什么你需要搜索这么大的范围才能找到一个范围。如果你要从你的IP中减去一些合理的数量,(到达网络的底部/顶部或其他地方),您可能会得到更快的结果,为范围查询指定一个最小/最大值。太好了。这对我来说很有效。查询大约330万个文档平均需要大约50毫秒。顺便说一句,在我的代码中,我添加了一个检查,以确保返回文档的start_ip_num小于或等于查询的ip地址。这是为了确保t确实有一份文件
db.collection_name.find({ ip_to: { $gte : ip_integer } }).sort({ ip_end: 1 }).limit(1)