我有一个用于城市ip块的MySQL表,
ip_ranges(city_id, CIDR, start_ip_decimal, end_ip_decimal)
数据类型如下
[unsigned bigint]
[varchar 255]
[unsigned bigint]
:启动ip范围转换为十进制[unsigned bigint]
:结束IP范围转换为十进制所以我正在做的是,将用户ip转换为十进制,并检查此表以获取city_id
。但查询需要花费太多时间 70秒来查找city_id
SELECT city_id FROM ip_ranges WHERE 658206441 BETWEEN start_ip_decimal and end_ip_decimal
或
SELECT city_id FROM ip_ranges WHERE start_ip_decimal <= 658206441 AND end_ip_decimal >= 658206441
注意: 658206441是从用户的IP地址转换的十进制值
InnoDB 用作数据库引擎。 此表中总共有 10664916 条记录。
Corei7 2.0GHz和2.6 GHz处理器,8 GB Ram(Windows 10)
所以我的问题是如何加快查找速度。
我尝试在开始和结束字段上应用索引
索引类型Normal
和索引方法BTREE
,但它没有任何影响。
这是DDL的样子
CREATE TABLE `ip_ranges` (
`cidr` varchar(255) DEFAULT NULL,
`start_ip_decimal` bigint(20) unsigned DEFAULT NULL,
`end_ip_decimal` bigint(20) unsigned DEFAULT NULL,
`city_id` bigint(20) unsigned DEFAULT NULL,
KEY `my_index` (`start_ip_decimal`,`end_ip_decimal`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
解决方案:我已根据@RickJames评论解决了(我认为是这样)我的问题,但我可能已经错过了他的回复的几个部分。
首先学到的是
像IP地址这样的范围测试很难。没有简单的索引可行 好。
所以我执行了以下步骤来实现我想要的目标。
1 忽略end
字段,经过Jame评论我意识到,每行start
字段等于前一行的end+1
。
但是,如果您只有一个起始IP而不是范围,那么 '结束'是下一行的开始,它可以做得更多 高效。
network start end
--------------------------------
1.0.0.0/24 16777216 16777471
1.0.1.0/24 16777472 16777727
1.0.2.0/23 16777728 16778239
然而,错过范围可能存在一些问题
2 使用UNIQUE
在index
列上应用start
键BTREE
3 修改后的SELECT
查询如下
SELECT * FROM ip_ranges
WHERE $ipNumberToCheck >= `start` ORDER BY `start` DESC LIMIT 1
答案 0 :(得分:1)
我希望它是INT UNSIGNED
(未签名)。
我认为你不担心IPv6?
像IP地址这样的范围测试很难。没有简单的索引效果很好。您拥有的KEY平均会扫描500万行。
但是,如果您只有一个起始IP而不是范围,并且'end'是下一行的开头,那么它可以更有效率。这也涉及ORDER BY ip LIMIT 1
。它涉及拥有未使用的IP范围的条目。我在my blog中介绍了所有这些以及有效的代码。它包括IPv4(就像您正在使用)和IPv6的存储例程。无论表大小如何,它都会在单行中找到该城市。因此它仅限于一次磁盘命中(粗略地说)。从逻辑上讲,它的速度是500万倍;但实际上70秒应该会下降到几毫秒。