【问题标题】:MARIADB: Index not used for a select with join on a rangeMARIADB:索引未用于在范围内连接的选择
【发布时间】:2016-11-17 17:48:42
【问题描述】:

我有一个包含存储为整数(500k 行)的 ips 的第一个表,第二个包含黑名单 ips 范围和黑名单原因(10M 行) 这是表结构:

    CREATE TABLE `black_lists` (
    `id` INT(11) NOT NULL AUTO_INCREMENT,
    `ip_start` INT(11) UNSIGNED NOT NULL,
    `ip_end` INT(11) UNSIGNED NULL DEFAULT NULL,
    `reason` VARCHAR(3) NOT NULL,
    `excluded` TINYINT(1) NULL DEFAULT NULL,
    PRIMARY KEY (`id`),
    INDEX `ip_range` (`ip_end`, `ip_start`),
    INDEX `ip_start` ( `ip_start`),
    INDEX `ip_end` (`ip_end`),
)
COLLATE='latin1_swedish_ci'
ENGINE=InnoDB
AUTO_INCREMENT=10747741
;

CREATE TABLE `ips` (
    `id` INT(11) NOT NULL AUTO_INCREMENT COMMENT 'Id ips',
    `idhost` INT(11) NOT NULL COMMENT 'Id Host',
    `ip` VARCHAR(45) NULL DEFAULT NULL COMMENT 'Ip',
    `ipint` INT(11) UNSIGNED NULL DEFAULT NULL COMMENT 'Int ip',
    `type` VARCHAR(45) NULL DEFAULT NULL COMMENT 'Type',
    PRIMARY KEY (`id`),
    INDEX `host` (`idhost`),
    INDEX `index3` (`ip`),
    INDEX `index4` (`idhost`, `ip`),
    INDEX `ipsin` (`ipint`)
)
COLLATE='latin1_swedish_ci'
ENGINE=InnoDB
AUTO_INCREMENT=675651;

我的问题是,当我尝试运行此查询时,没有使用索引,并且需要很长时间才能完成:

select i.ip,s1.reason
from ips i
    left join black_lists s1 on i.ipint BETWEEN s1.ip_start and s1.ip_end;

我正在使用 MariaDB 10.0.16

【问题讨论】:

  • 如果您编写的查询至少包含 一些 实际信息,从何处开始挖掘数据,则将使用索引。为什么不在问这样一个问题之前花 20 秒思考你做了什么?另外,您知道真正的索引是什么吗?让查询快速出乎意料并不是什么黑魔法。我建议您在就无法使用索引的查询寻求帮助之前,先挖掘一些有关索引是什么以及它们如何工作的信息,即使您有一个仙女许下的愿望 - 她无法使用此查询索引。
  • 我问这个问题是因为这个查询使用了索引:“select s1.reason from black_lists s1 on 111111111 BETWEEN s1.ip_start and s1.ip_end;”,所以我想如果我做一个左连接它会就像这个循环一样
  • 但是您在此处提供了一个 ,因此 MySQL 知道从哪里开始查找。如果您不提供实际值(例如 111111111 之类的数字),那么除了查看所有内容并吐出大量记录之外还能做什么?

标签: mysql indexing range left-join mariadb


【解决方案1】:

是的。

优化器不知道 start..end 值是不重叠的,也没有任何关于它们的明显信息。所以,它所能做的最好的选择是在

s1.ip_start <= i.ipint  -- and use INDEX(ip_start), or
s1.ip_end   >= i.ipint  -- and use INDEX(ip_end)

其中任何一个都可能导致一半以上的表被扫描。

通过 2 个步骤,您可以实现 一个 ip 的预期目标;假设@ip:

SELECT ip_start, reason
    FROM black_lists
    WHERE ip_start <= @ip
    ORDER BY ip_start DESC
    LIMIT 1

但是在那之后,你需要看看那个ip_start对应的ip_end是不是

SELECT reason
    FROM ( ... ) a   -- fill in the above query
    JOIN black_lists b  USING(ip_start)
    WHERE b.ip_end <= @ip

这将返回 reason 或不返回任何行。

尽管很复杂,但它会非常快。但是,您似乎有一组 IP 需要检查。这使它变得更加复杂。

对于black_lists,似乎不需要id。建议你只用 2 个替换 4 个索引:

PRIMARY KEY(ip_start, ip_end),
INDEX(ip_end)

ips 中,ip 不是唯一的吗?如果是这样,去掉 id 并将 5 个索引更改为 3 个:

PRIMARY KEY(idint),
INDEX(host, ip),
INDEX(ip)

您在 VARCHAR 中允许的 IPv6 已经足够多,但在 INT UNSIGNED 中却没有。

More discussion.

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2023-03-27
    • 2016-03-10
    • 1970-01-01
    • 1970-01-01
    • 2019-02-08
    • 1970-01-01
    • 2021-09-14
    • 1970-01-01
    相关资源
    最近更新 更多