2015-02-24 76 views
1

我正在学习一个由~2百万行+〜600k行(两个MyISAM表)组成的宠物项目的MySQL性能。在两个INT(10)索引列上使用BETWEEN进行范围查询,LIMIT为1返回的结果大约需要160ms(包括INNER JOIN)。我想我的配置没有优化,并且正在寻找一些关于如何进行诊断的建议,或者可能是“常见配置”。在200万行上的〜150ms MySQL MyISAM表

我创建了一个gist包含两个表,查询和my.cnf的内容。

在插入从MaxMinds open database的CSV文件导入的所有数据后,创建了b-tree索引。我试了两次,现在是一个综合指数,在性能上没有差异。

我在2.6GHz(i5)和8GB 1600MHz内存的MacBook Pro上本地运行此程序。 MySQL的安装使用可下载的二进制文件从mysql的下载页面(无法提供第三个链接,因为我的代表很低)。这是一个默认安装,对my.cnf配置文件没有主要的补充,包含在要点中(位于我的系统的/usr/local/mysql-5.6.xxx/目录下)。

我担心的是我达到了〜160ms,这表明我错过了一些东西。我曾考虑压缩桌子,但我有一种感觉,我错过了其他配置。另外myisampack不在我的PATH(我认为),所以我正在考虑其他优化,然后再进一步探索。

任何意见是赞赏!

$ mysql --version 
/usr/local/mysql-5.6.23-osx10.8-x86_64/bin/mysql Ver 14.14 Distrib 5.6.23, for osx10.8 (x86_64) using EditLine wrapper 

CREATE TABLE `blocks` (
    `id` int(11) unsigned NOT NULL AUTO_INCREMENT, 
    `begin_range` int(10) unsigned NOT NULL, 
    `end_range` int(10) unsigned NOT NULL, 
    `_location_id` int(11) unsigned DEFAULT NULL, 
    PRIMARY KEY (`id`), 
    KEY `begin_range` (`begin_range`,`end_range`) 
) ENGINE=MyISAM AUTO_INCREMENT=2008839 DEFAULT CHARSET=ascii; 

CREATE TABLE `locations` (
    `id` int(11) unsigned NOT NULL AUTO_INCREMENT, 
    `country` varchar(2) NOT NULL DEFAULT '', 
    `region` varchar(255) DEFAULT NULL, 
    `city` varchar(255) DEFAULT NULL, 
    `postalcode` varchar(255) DEFAULT NULL, 
    `latitude` float NOT NULL, 
    `longitude` float NOT NULL, 
    `metro_code` int(11) DEFAULT NULL, 
    `area_code` int(11) DEFAULT NULL, 
    PRIMARY KEY (`id`) 
) ENGINE=MyISAM AUTO_INCREMENT=641607 DEFAULT CHARSET=utf8; 

查询

SELECT locations.latitude, locations.longitude 
FROM blocks 
INNER JOIN locations ON blocks._location_id = locations.id 
WHERE INET_ATON('139.130.4.5') BETWEEN begin_range AND end_range 
LIMIT 0, 1; 

编辑; 在SELECT上更新了EXPLAIN的要点,为方便起见,这里也发布了。

EXPLAIN SELECT locations.latitude, locations.longitude FROM blocks INNER JOIN locations ON blocks._location_id = locations.id WHERE INET_ATON('94.137.106.123') BETWEEN begin_range AND end_range LIMIT 0, 1; 

+----+-------------+-----------+--------+---------------+-------------+---------+---------------------------+---------+------------------------------------+ 
| id | select_type | table  | type | possible_keys | key   | key_len | ref      | rows | Extra        | 
+----+-------------+-----------+--------+---------------+-------------+---------+---------------------------+---------+------------------------------------+ 
| 1 | SIMPLE  | blocks | range | begin_range | begin_range | 4  | NULL      | 1095345 | Using index condition; Using where | 
| 1 | SIMPLE  | locations | eq_ref | PRIMARY  | PRIMARY  | 4  | geoip.blocks._location_id |  1 | NULL        | 
+----+-------------+-----------+--------+---------------+-------------+---------+---------------------------+---------+------------------------------------+ 
2 rows in set (0.00 sec) 

编辑2;为了方便,将数据包含在问题中。

+0

您可能想要运行EXPLAIN并发布结果。 – 2015-02-24 09:44:19

+0

@ZsoltSzilagy谢谢,更新了这个问题。 – iwantoski 2015-02-24 09:52:49

+0

将索引作为'(begin_range,end_range)'是没有意义的。它会像使用'(begin_range)'一样使用。所以,最好是索引'(begin_range)',因为它比较小 - 从磁盘读取的字节数少(虽然好处相对较小)。你可以尝试在'(end_range)'上添加第二个索引,但是我怀疑MySQL会在这个查询中使用这两个索引。尝试并检查'解释'。 – 2015-02-24 10:19:13

回答

1

这个问题和正常的方法(你的代码的例证)导致命中1095345行。我有一个办法,可以做一个磁盘命中查询,即使缓存很冷。

摘录http://mysql.rjweb.org/doc.php/ipranges

现状

你的数据包括一大组不重叠“的范围”。这些可能是IP地址,日期时间(单站显示时间),邮编等。

你有一对开始和结束值;一个'项目'属于每个这样的'范围'。所以,本能地,你创建一个带有范围的开始和结束的表格,以及有关该项目的信息。您的查询涉及一个WHERE子句,用于比较开始值和结束值之间的差异。

问题

一旦获得大量项目,性能就会下降。你玩索引,但找不到任何效果。索引不能导致最佳功能,因为数据库不了解范围是不重叠的。

我将提供强制执行的事实,项不能有重叠的范围的溶液。该解决方案构建了一个表来利用该表,然后使用Stored Routine来解决它所施加的笨拙。