MySQL并不总是使用索引

时间:2015-11-25 08:26:03

标签: mysql database performance indexing

非常简单的问题但很难找到解决方案。 具有2,498,739行的地址表具有min_ip和max_ip字段的字段。这些是过滤表的核心锚点。

查询非常简单。

SELECT * 
FROM address a 
WHERE min_ip < value
  AND max_ip > value;

因此,为min_ip和max_ip创建索引以使查询更快是合乎逻辑的。

为以下内容创建索引。

CREATE INDEX ip_range ON address (min_ip, max_ip) USING BTREE;
CREATE INDEX min_ip ON address (min_ip ASC) USING BTREE;
CREATE INDEX max_ip ON address (max_ip DESC) USING BTREE;

我确实试图创建第一个选项(min_ip和max_ip的组合),但它没有用,所以我准备了至少3个索引,为MySQL提供了更多的索引选择选项。 (请注意,此表非常静态,更多的是查找表)

+------------------------+---------------------+------+-----+---------------------+-----------------------------+
| Field                  | Type                | Null | Key | Default             | Extra                       |
+------------------------+---------------------+------+-----+---------------------+-----------------------------+
| id                     | bigint(20) unsigned | NO   | PRI | NULL                | auto_increment              |
| network                | varchar(20)         | YES  |     | NULL                |                             |
| min_ip                 | int(11) unsigned    | NO   | MUL | NULL                |                             |
| max_ip                 | int(11) unsigned    | NO   | MUL | NULL                |                             |
+------------------------+---------------------+------+-----+---------------------+-----------------------------+

现在,应该直接使用min_ip和max_ip作为过滤条件来查询表。

EXPLAIN
SELECT * 
FROM address a 
WHERE min_ip < 2410508496
  AND max_ip > 2410508496;

查询执行了大约0.120到0.200秒的事情。但是,在负载测试中,查询会迅速降低性能。 MySQL服务器CPU使用率只需几个同时查询就可以达到100%的CPU使用率,并且性能会迅速降低并且不会扩展。 mysql服务器上的慢速查询已打开10秒或更长时间,最终选择查询在负载测试几秒钟后显示在日志中。 所以我用解释检查了查询,发现它确实没有使用索引。

解释计划结果

    id  select_type  table   type    possible_keys           key     key_len  ref        rows  Extra        
------  -----------  ------  ------  ----------------------  ------  -------  ------  -------  -------------
     1  SIMPLE       a       ALL     ip_range,min_ip,max_ip  (NULL)  (NULL)   (NULL)  2417789  Using where  

有趣的是,它能够将ip_range,ip_min和ip_max确定为潜在的索引,但从不使用任何一个,如关键列所示。 我知道我可以使用FORCE INDEX并尝试使用解释计划。

EXPLAIN
SELECT * 
FROM address a 
FORCE INDEX (ip_range)
WHERE min_ip < 2410508496
  AND max_ip > 2410508496;

使用FORCE INDEX结果解释计划

    id  select_type  table   type    possible_keys  key       key_len  ref        rows  Extra                  
------  -----------  ------  ------  -------------  --------  -------  ------  -------  -----------------------
     1  SIMPLE       a       range   ip_range       ip_range  4        (NULL)  1208894  Using index condition  

使用FORCE INDEX,是的,它使用ip_range索引作为键,行显示查询中不使用FORCE INDEX的子集,即2,417,789的1,208,894。 所以肯定,使用索引应该有更好的性能。 (除非我误解了解释结果)

但更有趣的是,经过几次测试后,我发现在某些情况下,即使没有FORCE INDEX,MySQL也会使用索引。我的观察是当值很小时,它会使用索引。

EXPLAIN
SELECT * 
FROM address a 
WHERE min_ip < 508496
  AND max_ip > 508496;

解释结果

    id  select_type  table   type    possible_keys           key       key_len  ref       rows  Extra                  
------  -----------  ------  ------  ----------------------  --------  -------  ------  ------  -----------------------
     1  SIMPLE       a       range   ip_range,min_ip,max_ip  ip_range  4        (NULL)       1  Using index condition  

所以,只是让我感到困惑的是,基于值传递给select查询,MySQL决定何时使用索引以及何时不使用索引。 我无法想象什么是确定何时使用索引传递给查询的特定值的基础。我明白这一点 如果在WHERE条件中没有匹配的索引,则可能不会使用index但在这种情况下,ip_range索引非常清楚 是一个基于min_ip的索引,max_ip列适用于这种情况下的WHERE条件。

但我遇到的更大问题是,其他查询呢?我是否必须大规模地测试这些查询。 但即便如此,随着数据的增长,我可以依赖并期望MySQL使用索引吗? 是的,我总是可以使用FORCE INDEX来确保它使用索引。但这不是适用于所有数据库的标准SQL。 ORM框架在生成SQL时可能无法支持FORCE INDEX语法,并且它会将您的查询与索引名称紧密耦合。

不确定是否有人遇到过这个问题,但这对我来说似乎是一个非常大的问题。

3 个答案:

答案 0 :(得分:3)

完全同意Vatev和其他人。 MySQL不仅如此。扫描表有时比先查看索引然后在磁盘上查找相应的条目要便宜。

当它确实使用索引的唯一时间是,当它是覆盖索引时,这意味着查询中的每一列(当然对于这个特定的表)都存在于索引中。意思是,如果您只需要网络列

SELECT network
FROM address a 
WHERE min_ip < 2410508496
  AND max_ip > 2410508496;

然后是一个覆盖索引,如

CREATE INDEX ip_range ON address (min_ip, max_ip, network) USING BTREE;

只会查看索引,因为根本不需要在磁盘上查找其他数据。并且整个索引可以保存在内存中。

答案 1 :(得分:0)

这样的范围非常难以优化。但我有a technique。它需要不重叠的范围,并且只存储start_ip,而不是end_ip(可以从&#39; next&#39;记录中有效获得)。它提供了存储的例程来隐藏凌乱的代码,涉及ORDER BY ... LIMIT 1和其他技巧。对于大多数操作而言,它不会击中多个数据块,这与那些倾向于获取一半或全部表格的明显方法不同。

答案 2 :(得分:0)

我同意上述所有答案。但你可以尝试只制作一个复合材料 像这样的索引:

create index ip_rang on address (min_ip ASC,max_ip DESC) using BTREE;

如您所知,索引也有使用磁盘空间的缺点,因此请考虑使用的最佳索引。