我们有一个包含大约350万个IP条目的MySQL表。
结构:
CREATE TABLE IF NOT EXISTS `geoip_blocks` (
`uid` int(11) NOT NULL auto_increment,
`pid` int(11) NOT NULL,
`startipnum` int(12) unsigned NOT NULL,
`endipnum` int(12) unsigned NOT NULL,
`locid` int(11) NOT NULL,
PRIMARY KEY (`uid`),
KEY `startipnum` (`startipnum`),
KEY `endipnum` (`endipnum`)
) TYPE=MyISAM AUTO_INCREMENT=3538967 ;
问题:查询需要3秒以上。
SELECT uid FROM `geoip_blocks` WHERE 1406658569 BETWEEN geoip_blocks.startipnum AND geoip_blocks.endipnum LIMIT 1
- 约3秒
SELECT uid FROM `geoip_blocks` WHERE startipnum < 1406658569 and endipnum > 1406658569 limit 1
- 没有收获,大约3秒
如何改进?
答案 0 :(得分:1)
你的startip和endip应该是一个综合索引。在一个查询中,Mysql无法在同一个表上使用多个索引。
我不确定语法,但是
KEY(startipnum,endipnum)
答案 1 :(得分:1)
看起来您正在尝试查找IP地址所属的范围。问题是MySQL无法充分利用BETWEEN操作的索引。使用=操作可以更好地使用索引。
您可以向查询添加=操作的一种方法是将network part of the address添加到表中。以你的例子:
numeric 1406658569
ip address 83.215.232.9
class A with 8 bit network part
network part = 83
使用(networkpart, startipnum, endipnum, uid)
上的索引,这样的查询会变得非常快:
SELECT uid
FROM `geoip_blocks`
WHERE networkpart = 83
AND 1406658569 BETWEEN startipnum AND endipnum
如果一个geoip块跨越多个网络类,则必须将每个网络类拆分为一行。
答案 2 :(得分:1)
根据您提问的信息,我假设您正在做的是MaxMind®的GeoIP®产品的实施。我下载了免费版的GeoIP®数据,将其加载到MySQL数据库中并进行了几次快速实验。
对于startipnum的索引,查询执行时间范围为0.15到0.25秒。在startipnum和endipnum上创建复合索引不会更改查询性能。这让我相信您的性能问题是由于硬件不足,MySQL调整不当或两者兼而有之。我运行测试的服务器有8G的RAM,这比获得与索引文件相同的性能所需要的要多得多,只有28M。
我的建议是以下两个选项之一。
另外,您提出的两个查询并不等同。运营商之间是包容性的。第二个查询需要使用&lt; =&gt; =运算符等效于使用between运算符的查询。
答案 3 :(得分:1)
解决方法是获取BTREE / ISAM库并使用它(如BerkelyDB)。使用ISAM这是一项微不足道的任务。
使用ISAM,您可以将开始键设置为数字,执行“查找下一个”,(查找块大于或等于您的号码),如果它不相等,您将“找到前一个”并检查该块。 3-4次磁盘命中,shazam,眨眼间完成。
嗯,这是一个解决方案。
这里发生的问题是没有“足够聪明的优化器”的SQL在这种查询上确实很糟糕。
例如,您的查询:
SELECT uid FROM `geoip_blocks` WHERE startipnum < 1406658569 and endipnum > 1406658569 limit 1
它将“查看”所有“小于”1406658569的行。所有这些行,然后它将扫描它们,寻找符合第二个标准的所有行。
使用3.5米的行表,假设“平均”(即它击中中间),欢迎进行1.75米的行表扫描。更糟糕的是,索引表扫描。理想情况下,MySQl将“放弃”和“只是”表扫描,因为它更快。
显然,这不是你想要的。
@Andomar的解决方案基本上是强迫您通过“网络”标准“阻止”数据空间。有效地将你的桌子打破了255件。因此,不是扫描1.75m行,而是扫描6800行,这是一个显着的改进,但代价是你在网络边界上破坏你的块。
SQL中的范围查询没有任何问题。
SELECT * FROM table WHERE id between X and Y
通常是快速查询,因为优化器可以使用索引轻松划分行范围。
但是,这不是你的查询,因为在这种情况下你没有使用主ID(startipnum)。
如果您“知道”您的块大小在一定范围内(即没有任何块,只有1000比1),那么您可以通过在{ipnum之间添加“WHERE startipnum”来阻止查询 - 1000}和{ipnum + 1000}“。这与提议的网络阻塞并没有什么不同,但是在这里你不必保持那么多。当然,您可以通过以下方式学习:
SELECT max(endipnum - startipnum) FROM table
了解你的最大范围。
我见过的另一种选择,从未使用过,但实际上,对于这一点来说是完美的,就是看MySql's Spatial Extensions,因为这就是它的真实含义。
这是为GIS应用程序设计的,但是你正在搜索范围内的东西,这是GIS应用程序的很多功能。所以,这也可能是你的解决方案。
答案 4 :(得分:0)
也许你想看看分区表。自MySQL 5.1以来,此功能已经可用 - 因此您没有指定您正在使用的版本,如果您遇到旧版本,这可能不适合您。
由于知道IP地址的可能值范围 - 至少对于IPv4 - 您可以将表分解为相同大小的多个分区(如果数据分布不均匀,甚至可能不相等)。由于MySQL可以很容易地跳过表的大部分内容,如果仍然需要扫描,则加快扫描速度。
有关可用选项和语法,请参阅MySQL manual on partitioning。
答案 5 :(得分:0)
感谢您的所有评论,我真的很感激。
现在我们最终使用了一种缓存机制,我们减少了那些昂贵的查询。