如何优化此范围查询

时间:2016-04-15 11:18:32

标签: mysql optimization

我有一张包含1500万条记录的表格,其中包含姓名,电子邮件地址和IP。我需要使用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条记录的查询需要大约15秒来执行,这意味着在所有数据库上运行相同的查询需要2天以上才能完成。有没有办法改进这个查询。

PS - 如果我删除了USE INDEX(idx_ip_from_to),则查询需要两倍的时间。你能解释一下原因吗?

此外,我不是数据库专家所以请耐心等待:)

2 个答案:

答案 0 :(得分: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。然后使用相等查询来获取行中的其余信息。

答案 1 :(得分: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_from值之前无法使用ip_to值(索引的第二部分)。因此,它仍然必须扫描大多数索引记录,以获得较低的ip_from值而没有上限。

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

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