我需要在location
表中的users
字段填写geoip
表中的国家/地区名称,具体取决于用户的IP。
这是表格'创建代码。
CREATE TABLE `geoip` (
`IP_FROM` INT(10) UNSIGNED ZEROFILL NOT NULL DEFAULT '0000000000',
`IP_TO` INT(10) UNSIGNED ZEROFILL NOT NULL DEFAULT '0000000000',
`COUNTRY_NAME` VARCHAR(50) NOT NULL DEFAULT '',
PRIMARY KEY (`IP_FROM`, `IP_TO`)
)
ENGINE=InnoDB;
CREATE TABLE `users` (
`id` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT,
`login` VARCHAR(25) NOT NULL DEFAULT ''
`password` VARCHAR(64) NOT NULL DEFAULT ''
`ip` VARCHAR(128) NULL DEFAULT ''
`location` VARCHAR(128) NULL DEFAULT ''
PRIMARY KEY (`id`),
UNIQUE INDEX `login` (`login`),
INDEX `ip` (`ip`(10))
)
ENGINE=InnoDB
ROW_FORMAT=DYNAMIC;
我尝试运行的更新查询是:
UPDATE users u
SET u.location =
(SELECT COUNTRY_NAME FROM geoip WHERE INET_ATON(u.ip) BETWEEN IP_FROM AND IP_TO)
问题是这个查询拒绝在PRIMARY
表上使用geoip
索引,尽管它会加快速度。 EXPLAIN给了我:
id select_type table type possible_keys key key_len ref rows Extra
1 PRIMARY u index NULL PRIMARY 4 NULL 1254395
2 DEPENDENT SUBQUERY geoip ALL PRIMARY NULL NULL NULL 62271 Using where
我最终只将geoip
表格转换为MEMORY引擎,但我想知道这样做的正确方法。
更新 我使用的DBMS是MariaDB 10.0.17,如果它可以有所作为。
答案 0 :(得分:0)
您是否尝试像这样强制索引
UPDATE users u
SET u.location =
(SELECT COUNTRY_NAME FROM geoip FORCE INDEX (PRIMARY)
WHERE INET_ATON(u.ip) BETWEEN IP_FROM AND IP_TO)
此外,由于ip可能为NULL,因此可能会搞乱索引优化。
答案 1 :(得分:0)
IP范围不重叠,对吗?您没有获得任何IPv6地址? (几年前,世界已经耗尽了IPv4。)
不,索引不会被使用,或者至少不会表现得如你所愿。所以,我已经设计了一个方案来解决这个问题。但是,它需要重新构建模式并构建存储例程。 See my IP-ranges blog;它具有IPv4和IPv6代码的链接。它通常只触摸表格中的一行,而不必扫描表格的一半。
修改强>
MySQL不知道只有一个范围(from / to)应该匹配。所以,它扫描得太多了。 IP的两种编码(INT UNSIGNED与VARCHAR)之间的差异使得难以使用JOIN(而不是子查询)。唉,JOIN不会更好,因为它不明白只有一个匹配。试一试:
UPDATE users u
SET u.location =
( SELECT COUNTRY_NAME
FROM geoip
WHERE INET_ATON(u.ip) BETWEEN IP_FROM AND IP_TO
LIMIT 1 -- added
)
如果无法显着提高速度,请在VARCHAR
中从INT UNSIGNED
更改为users
,然后重试(不使用INET_ATON
)。