【问题标题】:MySQL does not always use indexMySQL 并不总是使用索引
【发布时间】:2016-02-27 22:51:52
【问题描述】:

非常简单的问题,但很难找到解决方案。 具有 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 秒或更长时间内打开,最终在负载测试几秒钟后,选择查询显示在日志中。 所以我用explain检查了查询,发现它没有使用索引。

解释计划结果

    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 确定为潜在索引,但从未使用其中任何一个,如 key 列中所示。 我知道我可以使用 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  

所以,让我感到困惑的是,根据传递给选择查询的值,MySQL 决定何时使用索引以及何时不使用索引。 我无法想象确定何时在传递给查询的某个值上使用索引的基础是什么。我明白 如果没有适合 WHERE 条件的匹配索引,则可能不使用索引,但在这种情况下,很明显 ip_range 索引 是一个基于 min_ip 的索引,max_ip 列适合这种情况下的 WHERE 条件。

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

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

【问题讨论】:

  • 这看起来像一个基数问题。当返回的行大约是表的 30% 或更多时,mysql 将决定表扫描更好,忽略索引。索引仅用于返回一个小的行的比例。您的第一个查询返回 1208894 行,第二个只有 1 行
  • 如果基数较低,MySQL 将不会使用索引。基数是告诉您数据集中有多少唯一值的数字。 PK的基数是1,这是最大值。 MySQL 将此作为优化步骤。它确定索引是否会减少所需的查找量。
  • 感谢您的快速回复。在我看来,这是特定于 MySQL 实现的。不确定我是否关注基数,但 MySQL 如何确定基数?当我使用 2,410,508,496 的值时,它不使用索引,当我使用 508,496 时,它使用的索引很奇怪。
  • 如果我的数据库中的所有值都大于 2,410,508,496,索引应该告诉 MySQL 没有找到记录并跳过所有内容,因为 WHERE 条件表示 min_ip
  • 具体细节是特定于实现的,但所有 RDBMS 的原理都是相同的。为每个索引保留统计信息,并尝试猜测哪个搜索条件索引组合在表中出现的次数最少。它使用它来选择要使用的索引,如果它们都不能正常工作(超过 30% 的行),它只会扫描所有记录。因为是统计数据,所以不一定总能猜对。

标签: mysql database performance indexing


【解决方案1】:

完全同意 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;

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

【讨论】:

  • 这几乎解决了这个问题。我在想为什么表扫描比使用索引和@fancyPants 更有效,你刚刚明白了这个解释。索引没有所有数据,因为我的查询有 SELECT *(需要索引中没有的其他数据)并且仍然需要从表中获取这些数据。因此即使索引可以过滤记录,过滤器返回的记录仍然会从可能是随机访问IO(在数据块之间跳转)的表中访问。所以顺序访问整个表会更有效率。
  • 没关系,直到索引不适合内存。
【解决方案2】:

这样的范围很难优化。但我有a technique。它需要不重叠的范围,并且仅存储 start_ip,而不是 end_ip(可从“下一个”记录中有效获得)。它提供存储的例程来隐藏杂乱的代码,涉及ORDER BY ... LIMIT 1 和其他技巧。对于大多数操作,它不会访问超过一个数据块,这与倾向于获取一半或全部表的明显方法不同。

【讨论】:

    【解决方案3】:

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

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

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

    【讨论】:

      猜你喜欢
      • 2018-07-29
      • 2020-09-14
      • 1970-01-01
      • 2012-11-07
      • 2018-08-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-09-01
      相关资源
      最近更新 更多