我有一个 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”表包含 250 万行。 “range_begin”和“range_end”列都是唯一索引。 IP 范围似乎没有重叠。有时此查询需要大约 1 秒才能完成,但我希望有一种方法可以改进它,因为它是我网站上最慢的查询。
谢谢
编辑:我将查询更改为:
SELECT * FROM geoip
WHERE range_begin <= ".$iplong." AND range_end >= ".$iplong."
ORDER BY range_begin DESC LIMIT 1
我现在有一个 UNIQUE 综合索引(range_begin,range_end)。我使用了“EXPLAIN”函数,它看起来仍在搜索 120 万行:
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
最佳答案
我正在处理一个类似的问题,我必须搜索一个具有大约 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 上的索引。
关于MySQL:如何进行更快的 IP 范围查询?地理IP,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41079741/