2016-04-15 105 views
0

我有一个包含名称,电子邮件地址和IP的1500万条记录的表。我需要使用IP地址更新同一个表中国家代码的另一列。我下载了一个包含所有IP范围和相关国家的小型数据库(ip2location lite - https://lite.ip2location.com/)。 ip2location表具有以下结构;如何优化此范围查询

CREATE TABLE `ip2location_db1` (
    `ip_from` int(10) unsigned DEFAULT NULL, 
    `ip_to` int(10) unsigned DEFAULT NULL, 
    `country_code` char(2) COLLATE utf8_bin DEFAULT NULL, 
    `country_name` varchar(64) COLLATE utf8_bin DEFAULT NULL, 
KEY `idx_ip_from` (`ip_from`), 
KEY `idx_ip_to` (`ip_to`), 
KEY `idx_ip_from_to` (`ip_from`,`ip_to`) 
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_bin 

我使用以下函数从ip地址检索国家代码;

CREATE DEFINER=`root`@`localhost` FUNCTION `get_country_code`(
    ipAddress varchar(30) 
) RETURNS VARCHAR(2) 
    DETERMINISTIC 
    BEGIN 
     DECLARE ipNumber INT UNSIGNED; 
     DECLARE countryCode varchar(2); 
     SET ipNumber = SUBSTRING_INDEX(ipAddress, '.', 1) * 16777216; 
     SET ipNumber = ipNumber + (SUBSTRING_INDEX(SUBSTRING_INDEX(ipAddress, '.', 2),'.',-1) * 65536); 
     SET ipNumber = ipNumber + (SUBSTRING_INDEX(SUBSTRING_INDEX(ipAddress, '.', -2),'.',1) * 256); 
     SET ipNumber = ipNumber + SUBSTRING_INDEX(ipAddress, '.', -1); 

     SET countryCode = 
      (SELECT  country_code 
      FROM  ip2location.ip2location_db1 
      USE INDEX (idx_ip_from_to) 
      WHERE  ipNumber >= ip2location.ip2location_db1.ip_from AND ipNumber <= ip2location.ip2location_db1.ip_to 
      LIMIT  1); 

     RETURN countryCode; 
    END$$ 
DELIMITER ; 

我已经运行了EXPLAIN语句,这是输出;

'1', 'SIMPLE', 'ip2location_db1', NULL, 'range', 'idx_ip_from_to', 'idx_ip_from_to', '5', NULL, '1', '33.33', 'Using index condition' 

我的问题是,1000条记录查询采用15S〜执行这意味着运行在所有数据库中的相同的查询将需要超过2天就可以完成。有没有办法来改善这个查询。

PS - 如果我删除了USE INDEX(idx_ip_from_to),查询需要两倍的时间。你能解释为什么吗?

而且我不是一个数据库专家,所以容忍我:)

+0

表格是否有重叠范围?如果是这样,你不能优化它(即使戈登的建议)。 –

+0

不要对'country_code'使用'utf8' - 只需要2个时需要6个字节;使用'ascii'。 –

+0

IPv6怎么样? –

回答

0

这可能是相当棘手。我认为问题是只有ip_from部分条件可以使用。看是否有此得到表现你想要的:

SET countryCode = 
     (SELECT  country_code 
     FROM  ip2location.ip2location_db1 l 
     WHERE  ipNumber >= l.ip_from 
     ORDER BY ip_to 
     LIMIT  1 
     ); 

我知道我要走断ip_to。如果这样做,那么你可以做两个部分的全面检查。首先使用类似的查询获得ip_from。然后使用等式查询来获取行中其余的信息。

+0

谢谢...现在就试试 – claytonc

0

USE INDEX帮助的原因是因为MySQL不打算使用该索引。它的优化器选择了另一个,但它猜错了。有时会发生。

此外,我不确定这是否会影响性能吨,但您应该只使用INET_ATON将IP地址字符串更改为一个整数。您不需要SUBSTRING_INDEX业务,而且速度可能会更慢。

我会做什么这里是测量从和之间的最大距离:

SELECT MAX(ip_from - ip_to) AS distance 
FROM ip2location_db1; 

假设这不是一个愚蠢的号码,您将能够正常使用ip_from指数。支票就变成了:

WHERE ipNumber BETWEEN ip_from AND ip_from + distance 
    AND ipNumber <= ip_to 

这里的目标是让所有的信息来找到一个狭窄的一套行来自一个列的值的范围有限:ip_from。然后ip_to只是一个准确性检查。

你想这样做的原因是因为ip_to值(索引的第二部分)只有在找到相应的ip_from值后才能使用。所以它仍然需要扫描大部分索引记录以获得ip_from的低值,而没有上限。


否则,您可能会考虑测量您的1500万条记录中IP地址的唯一性。例如,如果只有500万个唯一的IP,则最好提取唯一列表,将它们映射到国家/地区代码,然后使用该映射(在运行时或更新原始表)。取决于。

如果值是本地化的集群非常独特的,但可能,你可以尝试从ip2location_db1,甚至水平分区删除无关行以提高范围检查。我不确定这会赢得什么,但是如果您可以在原始表格上使用某些索引来仅咨询特定分区,那么您可能会赢得一些性能。