【问题标题】:MySQL refuses to use index for UPDATE query with SELECT in SET clauseMySQL 拒绝在 SET 子句中使用 SELECT 的 UPDATE 查询索引
【发布时间】:2015-06-19 21:30:29
【问题描述】:

我需要用geoip 表中的国家名称填充users 表中的location 字段,具体取决于用户的IP。

这是表的 CREATE 代码。

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)

问题是这个查询拒绝在geoip 表上使用PRIMARY 索引,尽管它会加快速度。解释给了我:

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,如果它可以有所作为的话。

【问题讨论】:

  • 可能 inet_aton() 正在扔东西。您正在强制数据库即时转换您的所有 IP,因此无法使用索引 - 派生值未编制索引。
  • @MarcB,不幸的是,即使我使用计算出的 INET_ATON() 结果创建一个 INT 列并使用该列运行查询,查询计划也保持不变。
  • 这不是 inet_aton - 这也是我的第一个想法,但索引位于 ip_from 和 ip_to 列上,它可以很好地用于选择范围优化。据我所知,查询优化中可能会忽略 NULL 列,所以这是我目前的理论。
  • 我已经检查过了,NULL 也不是这里的情况。即使我使用 INT NOT NULL 列,它仍然会忽略索引。此外,如果我使用一些整数常量而不是实际的 ip 列,则会使用索引。那么,也许是一个错误?

标签: mysql mariadb query-performance


【解决方案1】:

您是否尝试过像这样强制索引

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,它可能会影响索引优化。

【讨论】:

  • 没错,我想让 MySQL 使用geoip 上的 PRIMARY 索引,这样国家/地区查找会更快。
【解决方案2】:

IP 范围不重叠,对吗?您没有获得任何 IPv6 地址? (几年前全世界的 IPv4 都用完了。)

不,索引不会被使用,或者至少不会像您希望的那样执行。所以,我设计了一个方案来解决这个问题。但是,它需要重新制定模式并构建存储例程。 See my IP-ranges blog;它具有指向 IPv4 和 IPv6 代码的链接。它通常只会触及表格中的一行,不必扫描一半的表格。

编辑

MySQL 不知道只有一个范围(从/到)应该匹配。所以,它扫描的太多了。 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
      )

如果不能显着提高速度,则将users 中的VARCHAR 更改为INT UNSIGNED,然后重试(不使用INET_ATON)。

【讨论】:

  • 这有点离题了。您提供的内容需要进行一些额外的更改,但在我的情况下,我最好将 geoip 表转换为 MEMORY 引擎并在大约 5 分钟内完成任务。我想知道的是为什么 MySQL 拒绝在子查询中使用索引,因为它会为 SELECT COUNTRY_NAME FROM geoip WHERE INET_ATON('xxx.xxx.xxx.xxx') BETWEEN IP_FROM AND IP_TO
猜你喜欢
  • 2016-09-30
  • 1970-01-01
  • 1970-01-01
  • 2016-12-21
  • 2013-10-26
  • 2012-10-28
  • 1970-01-01
  • 2013-11-15
  • 2018-10-06
相关资源
最近更新 更多