【问题标题】:Why does MySQL not always use index for select query?为什么 MySQL 不总是使用索引进行选择查询?
【发布时间】:2020-09-14 15:37:15
【问题描述】:

我的数据库用户和文章中有两个表。

我的用户和文章表中的记录如下:

+----+--------+
| id | name   |
+----+--------+
|  1 | user1  |
|  2 | user2  |
|  3 | user3  |
+----+--------+


+----+---------+----------+
| id | user_id | article  |
+----+---------+----------+
|  1 |       1 | article1 |
|  2 |       1 | article2 |
|  3 |       1 | article3 |
|  4 |       2 | article4 |
|  5 |       2 | article5 |
|  6 |       3 | article6 |
+----+---------+----------+

给出下面的查询和受人尊敬的EXPLAIN 输出。

EXPLAIN SELECT * FROM articles WHERE user_id = 1;

+----+-------------+----------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table    | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra       |
+----+-------------+----------+------------+------+---------------+------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | articles | NULL       | ALL  | user_id       | NULL | NULL    | NULL |    6 |    50.00 | Using where |
+----+-------------+----------+------------+------+---------------+------+---------+------+------+----------+-------------+



EXPLAIN SELECT * FROM articles WHERE user_id = 2;
+----+-------------+----------+------------+------+---------------+---------+---------+-------+------+----------+-------+
| id | select_type | table    | partitions | type | possible_keys | key     | key_len | ref   | rows | filtered | Extra |
+----+-------------+----------+------------+------+---------------+---------+---------+-------+------+----------+-------+
|  1 | SIMPLE      | articles | NULL       | ref  | user_id       | user_id | 5       | const |    2 |   100.00 | NULL  |
+----+-------------+----------+------------+------+---------------+---------+---------+-------+------+----------+-------+


EXPLAIN SELECT * FROM articles WHERE user_id = 3;
+----+-------------+----------+------------+------+---------------+---------+---------+-------+------+----------+-------+
| id | select_type | table    | partitions | type | possible_keys | key     | key_len | ref   | rows | filtered | Extra |
+----+-------------+----------+------------+------+---------------+---------+---------+-------+------+----------+-------+
|  1 | SIMPLE      | articles | NULL       | ref  | user_id       | user_id | 5       | const |    1 |   100.00 | NULL  |
+----+-------------+----------+------------+------+---------------+---------+---------+-------+------+----------+-------+

查看我的选择查询的EXPLAIN 计划,查询似乎并不总是使用索引。

万一,

user_id为1时,不使用key,扫描全表。

否则,它使用user_id 键并且只扫描几行。

您能否解释一下为什么查询并不总是在这里使用索引?

【问题讨论】:

  • 那是 mysql 优化器的领域。 dev.mysql.com/doc/refman/8.0/en/mysql-indexes.htmlIn some cases, a query can be optimized to retrieve values without consulting the data rows. 和文章底部Indexes are less important for queries on small tables, or big tables where report queries process most or all of the rows.
  • 您的表的行数太少,以至于使用索引最终可能比一次读取整个数据更昂贵。当您检索 1) 几行,2) 从大表中检索时,索引的性能非常好;您的示例中不满足第二个条件。
  • 感谢您明确说明。我明白了。但我仍然对查询的不一致行为感到困惑。使用索引可能最终会更昂贵,那么为什么在 user_id 为 2 或 3 时使用索引进行查询?
  • 因为它取决于值的选择性 - 如果 1 的 user_id 比值 2 或 3 更常见,则优化器可能会认为值 1 具有较低的选择性,因此它将是最好进行全表扫描。此行为由在 INSERT/UPDATE/DELETE 期间收集的统计信息控制。看看这些文章dev.mysql.com/doc/refman/8.0/en/statistics-table.htmlpercona.com/blog/2017/09/11/…
  • 请提供SHOW CREATE TABLE articles

标签: mysql database query-optimization sql-execution-plan explain


【解决方案1】:

您显示的查询中涉及(可能)两个 BTree。数据的一个 BTree,按PRIMARY KEY 排序,我假设它是id。另一个是user_id 上的INDEX(我再次猜测)。当 InnoDB(我假设您正在使用)构建“二级索引”时,例如 INDEX(user_id),它会默默地添加表的 PK。因此,它实际上变成了一个 BTree 包含两列:(user_id, id) 并按该对排序。

当优化器查看SELECT * FROM t WHERE user_id=? 时,它会探测表并发现“很多”行具有user_id = 1,而没有多少行具有您尝试的其他值。

优化器有两种(或更多)方法来评估这样的查询 --

A 计划(使用索引):它的作用如下:

  1. 向下钻取索引的 BTree 以找到 user_id=2 的第一次出现。
  2. 在那里它会找到一个id
  3. 使用该id 向下钻取数据的BTree 以找到*(如SELECT *)。
  4. 转到索引 BTree 中的下一个条目。 (这实际上是相当有效的,因为它实际上是一个“B+树”;参见 Wikipedia。)
  5. 如果找到,则循环回到步骤 2。如果未找到(没有更多带有 user_id=2 的索引条目),则退出。

B 计划(不要使用索引——对您的user_id=1 有用):

  1. 只需按任意顺序遍历数据 BTree。
  2. 跳过任何没有user_id=1 的行。

在两个 BTree 之间来回弹跳需要付出一些代价。优化器决定您的=1 案例需要查看超过大约 20% 的表格,并决定 B 计划会更快。也就是说,它故意忽略了 INDEX。

优化器无法或无法正确估计许多因素,但通常在这两个计划之间进行选择可以加快执行速度。 (您的表格太小,无法可靠地衡量差异。)

其他“计划”——如果索引是“覆盖”,则不需要使用数据BTree。如果有可以使用的ORDER BY,那么优化器将可能使用计划 A 来避免“文件排序”。 (见EXPLAIN SELECT ...)等

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-11-09
    • 1970-01-01
    • 2014-02-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多