包含350万条目的数据库表 - 我们如何才能提高性能?

时间:2009-11-11 11:00:05

标签: mysql

我们有一个包含大约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秒

如何改进?

6 个答案:

答案 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。

我的建议是以下两个选项之一。

  1. 花些时间调整MySQL服务器。 MySQL在线文档将是一个合理的起点。 http://dev.mysql.com/doc/refman/5.0/en/optimizing-the-server.html如果MySQL文档不充分,互联网搜索将会出现大量的书籍,论坛,文章等。
  2. 如果我的假设是正确的,您使用的是GeoIP®产品,那么第二种选择是使用MaxMind®提供的二进制文件格式。自定义文件格式已针对速度,内存使用和数据库大小进行了优化。用于访问数据的API是针对多种语言提供的。 http://www.maxmind.com/app/api
  3. 另外,您提出的两个查询并不等同。运营商之间是包容性的。第二个查询需要使用&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)

感谢您的所有评论,我真的很感激。

现在我们最终使用了一种缓存机制,我们减少了那些昂贵的查询。