MySQL拒绝在SET子句中使用带有SELECT的UPDATE查询索引

时间:2015-04-14 10:04:43

标签: mysql mariadb query-performance

我需要在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,如果它可以有所作为。

2 个答案:

答案 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)。