【问题标题】:How to optimize this range query如何优化此范围查询
【发布时间】:2016-04-17 13:15:47
【问题描述】:

我有一个包含 1500 万条记录的表,其中包含姓名、电子邮件地址和 IP。我需要使用 IP 地址使用国家/地区代码更新同一张表中的另一列。我下载了一个小型数据库(ip2location lite - https://lite.ip2location.com/),其中包含所有 IP 范围和相关国家/地区。 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) 查询需要两倍的时间。你能解释一下原因吗?

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

【问题讨论】:

  • 表格是否有重叠范围?如果是这样,你就无法优化它(即使有 Gordon 的建议)。
  • 不要将utf8 用于country_code——当你只需要2个字节时它需要6个字节;使用ascii
  • 归一化country_name;它把桌子弄得乱七八糟。
  • (我向 ip2location.com 发送了关于 country_code 的评论。)

标签: mysql optimization


【解决方案1】:

这可能非常棘手。我认为问题在于只能使用条件的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。然后使用相等查询来获取行中的其余信息。

【讨论】:

  • 谢谢...现在试试
【解决方案2】:

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 低值。


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

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

【讨论】:

    猜你喜欢
    • 2017-03-01
    • 2023-04-02
    • 1970-01-01
    • 2014-09-29
    • 1970-01-01
    相关资源
    最近更新 更多