我有一张包含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),则查询需要两倍的时间。你能解释一下原因吗?
此外,我不是数据库专家所以请耐心等待:)
答案 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中删除不相关的行,甚至可以尝试从水平分区中删除以改进范围检查。我不确定这会赢得什么,但如果您可以使用原始表上的某个索引来仅查询特定分区,那么您可能会赢得一些性能。