如何同时使用$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)