为啥 MySQL 不使用我的索引进行 JOIN?

Posted

技术标签:

【中文标题】为啥 MySQL 不使用我的索引进行 JOIN?【英文标题】:Why is MySQL not using my index for JOIN?为什么 MySQL 不使用我的索引进行 JOIN? 【发布时间】:2020-10-19 16:16:25 【问题描述】:

我有一个由 ORM 生成的非常复杂的查询,但是为了这个问题,这里是相关部分:

SELECT
    ...
FROM
    `Broadcast` `t`
    LEFT OUTER JOIN
        `Site` `site`
        ON (`t`.`site_id` = `site`.`id`)
    LEFT OUTER JOIN
        `Customer` `customer`
        ON (`site`.`customer_id` = `customer`.`id`)
    LEFT OUTER JOIN
        `Domain` `domain`
        ON (`customer`.`domain_id` = `domain`.`id`)
    ...

我的字段定义如下:

>SHOW FIELDS FROM Broadcast;
+---------------------------+--------------------------------------+------+-----+------------------+----------------+
| Field                     | Type                                 | Null | Key | Default          | Extra          |
+---------------------------+--------------------------------------+------+-----+------------------+----------------+
| id                        | int(10) unsigned                     | NO   | PRI | NULL             | auto_increment |
| site_id                   | int(10) unsigned                     | NO   | MUL | NULL             |                |
...

>SHOW FIELDS FROM Site;
+---------------------------+---------------------+------+-----+------------------+----------------+
| Field                     | Type                | Null | Key | Default          | Extra          |
+---------------------------+---------------------+------+-----+------------------+----------------+
| id                        | int(10) unsigned    | NO   | PRI | NULL             | auto_increment |
| customer_id               | int(11)             | NO   | MUL | 0                |                |
...

>SHOW FIELDS FROM Customer;
+---------------------------+------------------+------+-----+---------+----------------+
| Field                     | Type             | Null | Key | Default | Extra          |
+---------------------------+------------------+------+-----+---------+----------------+
| id                        | int(11)          | NO   | PRI | NULL    | auto_increment |
| domain_id                 | int(10) unsigned | NO   | MUL | 1       |                |
...

>SHOW FIELDS FROM Domain;
+---------------------------------+------------------+------+-----+---------+----------------+
| Field                           | Type             | Null | Key | Default | Extra          |
+---------------------------------+------------------+------+-----+---------+----------------+
| id                              | int(10) unsigned | NO   | PRI | NULL    | auto_increment |
...

为什么我们的一些 ID 是 int(11) 而另一些是 int(10) unsigned 只是历史背景问题。重要的是X_id 外键始终与相关的id 列共享一个类型。我们从不尝试将int(11)int(10) unsigned 链接。此外,id 字段始终是表的主键。

我们还有所有外键的索引:

>SHOW INDEXES FROM Broadcast;
+-----------+------------+---------------------------------------+--------------+---------------------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| Table     | Non_unique | Key_name                              | Seq_in_index | Column_name               | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
+-----------+------------+---------------------------------------+--------------+---------------------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| Broadcast |          0 | PRIMARY                               |            1 | id                        | A         |      139708 |     NULL | NULL   |      | BTREE      |         |               |
| Broadcast |          1 | site_id                               |            1 | site_id                   | A         |        1060 |     NULL | NULL   |      | BTREE      |         |               |
...

>SHOW INDEXES FROM Site;
+-------+------------+----------------------------------+--------------+---------------------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| Table | Non_unique | Key_name                         | Seq_in_index | Column_name               | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
+-------+------------+----------------------------------+--------------+---------------------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| Site  |          0 | PRIMARY                          |            1 | id                        | A         |        1876 |     NULL | NULL   |      | BTREE      |         |               |
| Site  |          1 | customer_id                      |            1 | customer_id               | A         |         250 |     NULL | NULL   |      | BTREE      |         |               |
...

>SHOW INDEXES FROM Customer;
+----------+------------+--------------------------------------+--------------+---------------------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| Table    | Non_unique | Key_name                             | Seq_in_index | Column_name               | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
+----------+------------+--------------------------------------+--------------+---------------------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| Customer |          0 | PRIMARY                              |            1 | id                        | A         |         292 |     NULL | NULL   |      | BTREE      |         |               |
| Customer |          1 | domain_id                            |            1 | domain_id                 | A         |           2 |     NULL | NULL   |      | BTREE      |         |               |
...

>SHOW INDEXES FROM Domain;
+--------+------------+-------------------------------------------+--------------+---------------------------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| Table  | Non_unique | Key_name                                  | Seq_in_index | Column_name                     | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
+--------+------------+-------------------------------------------+--------------+---------------------------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| Domain |          0 | PRIMARY                                   |            1 | id                              | A         |           2 |     NULL | NULL   |      | BTREE      |         |               |
...

这些索引中的每一个都包含一个字段,因此该字段是索引中的第一个序列。

当我在查询中运行 EXPLAIN ... 时,我得到的输出如下所示:

Site 和 Customer 使用 eq_ref 类型连接,但 Domain 使用 ALL 类型连接。为什么 mysql 不使用我的索引?

我可以通过在 Customer 表名后添加 USE INDEX FOR JOIN (domain_id) 来强制 MySQL 使用我的索引,但这会导致一个新问题:

问题只是从Customer->Domain 关系上升到Site->Customer 关系。我可以在 Site 表名称之后使用 USE INDEX FOR JOIN (customer_id) 将问题进一步上移,正如您所料,这只会导致 Site 表与 ALL 连接输入而不是eq_ref

我一辈子都搞不明白 MySQL 拒绝使用我的索引的原因

【问题讨论】:

域是行数很少的表吗?优化器可以决定对这样的表使用任何索引根本不值得 @ysth 是的,域表有 1 行或 2 行,具体取决于该系统运行的环境。那么当行数很少时不使用索引实际上更好吗? 是的。或者至少不值得担心 【参考方案1】:

每个 cmets 中的 @ysth:

如果您要加入的表的行数很少,MySQL 将不会使用索引,因为从磁盘读取索引的开销超过了好处

【讨论】:

以上是关于为啥 MySQL 不使用我的索引进行 JOIN?的主要内容,如果未能解决你的问题,请参考以下文章

为啥 MySQL 中的这个查询不使用索引?

MySQL - 为啥不索引每个字段?

获取不使用索引的连接的 MySQL 查询列表

mysql中多表连接(不使用join )为啥 超过三张表就报错?

MySQL 在结合 join 和 range 时不使用整个索引

mysql join不使用'between'运算符的索引