繁体   English   中英

MySQL:如何进行更快的 IP 范围查询? 地理IP

[英]MySQL: How to make a faster IP range query? GeoIP

我有一个 PHP/MySQL geo-ip 脚本,它获取用户的 IP 地址,将其转换为长整数并在 IP 范围表中搜索用户 IP 所在位置的单个地理位置 ID:

$iplong = ip2long($_SERVER['REMOTE_ADDR']);

SELECT id FROM geoip
 WHERE ".$iplong." BETWEEN range_begin AND range_end
 ORDER BY range_begin DESC LIMIT 1

“geoip”表包含 2.5M 行。 “range_begin”和“range_end”列都是唯一索引。 IP 范围似乎没有重叠。 有时这个查询需要大约 1 秒才能完成,但我希望有一种方法可以改进它,因为它是我网站上最慢的查询。

谢谢

编辑:我将查询更改为:

SELECT * FROM geoip
 WHERE range_begin <= ".$iplong." AND range_end >= ".$iplong."
 ORDER BY range_begin DESC LIMIT 1

我现在有一个唯一的综合指数(range_begin、range_end)。 我使用了“EXPLAIN”函数,看起来它仍然搜索 1.2M 行:

id: 1
select_type: Simple
table: geoip
type: range
possible_keys: range_begin
key: range_begin
key_len: 8
ref: NULL
rows: 1282026
Extra: Using Index Condition

花一些时间思考为什么传统索引在这样的场景中毫无用处,这是一个非常有用的练习。 实际上,如果您可以让查询使用索引,您会发现它可能比运行全表扫描慢。

解释为什么会占用比此处可用空间更多的空间。 有一个解决方案——将 ipaddress 数据库视为一维空间并使用空间索引。 但MySQL的空间索引仅在2个维度的工作-所以你需要映射所描述的坐标转换为2维空间在这里

请注意,当您开始处理嵌套子网时,大于 / 限制方法虽然比空间索引快,但会变得混乱。

在我查看数据之前,还有一种更简单的方法让我逃过一劫。

第一次运行

SELECT * FROM Ip2location WHERE ip_from <= $IPAddress ORDER BY ip_from DESC LIMIT 1

这将返回等于或小于您正在搜索的值的最近 IP,因为您将返回数据库中的最高单个值。

接下来,当您返回整行时,只需确保 ip_to 大于或等于 IP,您就可以确保 IP 落在该范围内。

如果 IP 不在该范围内(如果 ip_to 较小),则表示没有该 IP 的记录。

简单且快速执行!

我正在处理一个类似的问题,我不得不搜索一个包含大约 400 万个 IP 范围的数据库,并找到了一个很好的解决方案,将扫描的行数从 400 万个减少到大约 5 个(取决于 IP):

此 SQL 语句:

SELECT id FROM geoip WHERE $iplong BETWEEN range_begin AND range_end 

转化为:

SELECT id FROM geoip WHERE range_begin <= $iplong AND range_end >= $iplong 

问题是 MySQL 检索所有带有 'range_begin <= $iplong' 的行,然后如果 'range_end >= $iplong' 需要扫描。 第一个 AND 条件 (range_begin <= $iplong) 检索了大约 200 万行,如果 range_end 匹配,则需要检查所有行。

然而,这可以通过添加一个 AND 条件来显着简化:

SELECT id FROM geoip WHERE range_begin <= $iplong AND range_begin >= $iplong-65535 AND range_end >= $iplong 

该声明

range_begin <= $iplong AND range_begin >= $iplong-65535

仅检索 range_begin 介于 $iplong-65535 和 $iplong 之间的条目。 就我而言,这将检索到的行数从 4 Mio 减少了。 到大约 5,脚本运行时间从几分钟缩短到几秒钟。

关于 65535 的注意事项:这是我的表中 range_begin 和 range_end 之间的最大距离,即,对于我的所有行, (range_end-range_begin) <= 65535。 如果 IP 范围较大,则必须增加 65535,如果 IP 范围较小,则可以减小此常数。 如果这个常数太大(例如 40 亿),您将不会节省任何查询时间。

对于此查询,您只需要 range_begin 上的索引。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM