MySQL:查询之间的最佳索引

Jes*_*run 10 mysql indexing

我有一个具有以下结构的表:

CREATE TABLE `geo_ip` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `start_ip` int(10) unsigned NOT NULL,
  `end_ip` int(10) unsigned NOT NULL,
  PRIMARY KEY (`id`),
  KEY `start_ip` (`start_ip`),
  KEY `end_ip` (`end_ip`),
  KEY `start_end` (`start_ip`,`end_ip`),
  KEY `end_start` (`end_ip`,`start_ip`)) ENGINE=InnoDB;
Run Code Online (Sandbox Code Playgroud)

MySQL的似乎是无法使用我的大多数查询的指标,作为where第一个使用between落在介于start_ipend_ip:

select * from geo_ip where 2393196360 between start_ip and end_ip;

+----+-------------+--------+------+-------------------------------------+------+---------+------+---------+-------------+
| id | select_type | table  | type | possible_keys                       | key  | key_len | ref  | rows    | Extra       |
+----+-------------+--------+------+-------------------------------------+------+---------+------+---------+-------------+
|  1 | SIMPLE      | geo_ip | ALL  | start_ip,end_ip,start_end,end_start | NULL | NULL    | NULL | 2291578 | Using where |
+----+-------------+--------+------+-------------------------------------+------+---------+------+---------+-------------+
Run Code Online (Sandbox Code Playgroud)

该表有几百万条记录.我试图扩大该表,通过移除start_ipend_ip列,并为每一个可能值创建一行start_ip,并end_ip作为id,然后查询id.虽然这大大提高了查询性能,但它导致表大小从不到1千兆字节增长到数十千兆字节(表中显然还有其他列).

还有什么可以提高查询性能?我可以以某种方式更改查询,还是可以不同地索引列以导致命中?或者也许是我还没有想到的东西?

编辑:

奇怪的是,索引用于某些值.例如:

explain select * from geo_ip where 3673747503 between start_ip and end_ip;
+----+-------------+--------+-------+-------------------------------------+--------+---------+------+-------+-------------+
| id | select_type | table  | type  | possible_keys                       | key    | key_len | ref  | rows  | Extra       |
+----+-------------+--------+-------+-------------------------------------+--------+---------+------+-------+-------------+
|  1 | SIMPLE      | geo_ip | range | start_ip,end_ip,start_end,end_start | end_ip | 4       | NULL | 19134 | Using where |
+----+-------------+--------+-------+-------------------------------------+--------+---------+------+-------+-------------+
Run Code Online (Sandbox Code Playgroud)

Jes*_*run 11

不知道为什么,但添加order by子句和限制查询似乎总是导致索引命中,并在几毫秒而不是几秒内执行.

explain select * from geo_ip where 2393196360 between start_ip and end_ip order by start_ip desc limit 1;
+----+-------------+--------+-------+-----------------+----------+---------+------+--------+-------------+
| id | select_type | table  | type  | possible_keys   | key      | key_len | ref  | rows   | Extra       |
+----+-------------+--------+-------+-----------------+----------+---------+------+--------+-------------+
|  1 | SIMPLE      | geo_ip | range | start_ip,end_ip | start_ip | 4       | NULL | 975222 | Using where |
+----+-------------+--------+-------+-----------------+----------+---------+------+--------+-------------+
Run Code Online (Sandbox Code Playgroud)

这对我来说已经足够了,虽然我很想知道为什么优化器决定不在其他情况下使用索引的原因.

  • 我有一个类似的查询和限制的顺序也使它快10倍.很想知道是否有人可以对此有所了解.使用EXPLAIN的查询计划显示几乎相同(使用索引,使用where),但是order/limit显示'range'类型,其他是'all'类型 (3认同)

Dan*_*iel 7

我遇到了同样的问题。由于没有人回答“为什么”,并且我已经弄清楚了,因此在这里我将为所有未来的读者提供解释。

首先,让我们剖析查询。

where 2393196360 between start_ip and end_ip
Run Code Online (Sandbox Code Playgroud)

真正意思

where start_ip <= C and end_ip >= C
Run Code Online (Sandbox Code Playgroud)

因此引擎将首先使用on索引start_ip, end_ip来获取start_ip小于C的所有行,然后进一步过滤掉end_ip也大于C的行。

当引擎寻找start_ip <= C,并且C其值足够大以至于大多数或所有start_ips都小于C时,此“第一遍”将导致很多行。每当CIP范围较高的IP 都将发生这种情况。

现在,这是要实现的主要内容:我们的数据集的制作方式是,对于每个start_ip,只有一个end_ip值,并且保证该end_ip值小于下一条记录的start_ip值。我们正在划分范围,并且分区不重叠。但是,在一般情况下,当涉及到两个表字段时,就不必如此!

因此,在“首次通过”之后,引擎将不得不浏览所有匹配的记录,start_ip <= C以确保end_ip >= C即使有索引,它们也都匹配。end_ip在我们的案例中,拥有作为复合指数的一部分并没有多大作用;仅当end_ip每个值都有多个值start_ip,但只有1个时,它才有帮助。举一个例子,假设列中填充了以下数据:

start_ip  end_ip
1         10001
1         10002
1         10003
------------
2         10001
2         10002
2         10003
------------
...
------------
9999      10001
9999      10002
9999      10003
Run Code Online (Sandbox Code Playgroud)

如果您使用进行查询start_ip <= 10000 AND end_ip >= 10000,请注意所有行均与表达式匹配。另一方面,在本例中start_ip <= C AND end_ip >= C,由于ip数据的结构方式,对于ip-ranges数据集,我们可以保证只有ONE记录将与任何表达式匹配。特别是start_ip在所有匹配的记录中,具有最大价值的记录start_ip <= C。这就是为什么在这种情况下添加ORDER BY和LIMIT 1的原因,并且我认为这是最干净的解决方案。


编辑:我刚刚注意到,在某些情况下,添加ORDER BY start_ip DESC和LIMIT子句可能不够。如果您使用数据中任何范围都未覆盖的值(例如,使用127.0.0.1或192.168。*这样的私有IP)运行查询,则引擎仍会查看与该start_ip <= C表达式匹配的所有记录,并且查询将慢一点 这是因为由于没有记录与表达式(end_ip >= C)的第二部分匹配,所以LIMIT 1子句永远不会插入。

我发现的解决方案是使用联接构造查询,以强制引擎首先获取start_ipwhere 值最大的记录start_ip <= C,然后才检查end_ip是否也> =C。像这样:

SELECT * 
FROM 
  ( select id FROM geo_ip WHERE start_ip <= C ORDER BY start_ip DESC LIMIT 1 ) limit_ip
  INNER JOIN geo_ip ON limit_ip.id = geo_ip.id
WHERE geo_ip.end_ip >= C
Run Code Online (Sandbox Code Playgroud)

无论C表中的范围是否覆盖特定的ip,此查询都将执行一次查找,并且只需要打开一个索引start_ip(以及id主键)。