MySQL EXPLAIN 显示密钥未被使用。它在做什么吗?

MySQL EXPLAIN shows key not being used. Is it doing anything at all?

假设我有三个 tables:shipmentscustomersstores. shipments table 有两个索引:customer_id 类型的 INT(引用客户 table),和日期时间类型的 datecustomers table 有一个索引:store_id 类型的 INT(引用商店 table)。

如果我按日期过滤发货,我会看到正在使用 日期 索引:

EXPLAIN extended SELECT * FROM shipments
WHERE date >= '2020-04-01' AND date <= '2020-05-01';

+----+-------------+-----------+-------+---------------+------+---------+-------+--------+----------+-------------+
| id | select_type | table     | type  | possible_keys | key  | key_len | ref   | rows   | filtered | Extra       |
+----+-------------+-----------+-------+---------------+------+---------+-------+--------+----------+-------------+
|  1 | SIMPLE      | shipments | range | date          | date | 9       | NULL  | 250796 |   100.00 | Using where |
+----+-------------+-----------+-------+---------------+------+---------+-------+--------+------------------------+

然而,接下来这两个查询的输出让我感到困惑,因为它们几乎是一样的:

EXPLAIN extended SELECT shipments.* FROM shipments
LEFT JOIN customers ON shipments.customer_id = customers.id
WHERE customers.store_id = 100 AND 
shipments.date >= '2020-04-01 00:0:00.0' AND shipments.date <= '2020-05-01 00:0:00.0';

+----+-------------+-----------+-------+-------------------+-------------+---------+---------------+--------+----------+--------------------------+
| id | select_type | table     | type  | possible_keys     | key         | key_len | ref           | rows   | filtered | Extra                    |
+----+-------------+-----------+-------+-------------------+-------------+---------+---------------+--------+----------+--------------------------+
|  1 | SIMPLE      | customers | ref   | PRIMARY, store_id | store_id    | 5       | const         | 38     |   100.00 | Using where; Using index |
+----+-------------+-----------+-------+-------------------+-------------+---------+---------------+--------+----------+--------------------------+
|  1 | SIMPLE      | shipments | ref   | customer_id, date | customer_id | 5       | customers.id  | 663    |   100.00 | Using where              |
+----+-------------+-----------+-------+-------------------+-------------+---------+---------------+--------+-------------------------------------+

EXPLAIN extended SELECT shipments.* FROM shipments
LEFT JOIN customers ON shipments.customer_id = customers.id
WHERE customers.store_id = 100;

+----+-------------+-----------+-------+-------------------+-------------+---------+---------------+--------+----------+--------------------------+
| id | select_type | table     | type  | possible_keys     | key         | key_len | ref           | rows   | filtered | Extra                    |
+----+-------------+-----------+-------+-------------------+-------------+---------+---------------+--------+----------+--------------------------+
|  1 | SIMPLE      | customers | ref   | PRIMARY, store_id | store_id    | 5       | const         | 38     |   100.00 | Using where; Using index |
+----+-------------+-----------+-------+-------------------+-------------+---------+---------------+--------+----------+--------------------------+
|  1 | SIMPLE      | shipments | ref   | customer_id       | customer_id | 5       | customers.id  | 663    |   100.00 | Using where              |
+----+-------------+-----------+-------+-------------------+-------------+---------+---------------+--------+-------------------------------------+

问题 1:此输出是否意味着这两个查询中的第一个根本不使用 date 索引?我读到 MySQL 每个 table 不会使用超过一个索引,所以我的 date 索引在性能方面有什么不同吗? (在我的程序中,所有按日期范围过滤的查询看起来都非常像那个查询)假设有大量的客户和大量的货物以及大量的查询同时启动,我应该如何提高性能?

问题 2:为什么这两个查询的输出中 'rows' 的值相同,如果第一个比第一个意味着更多的过滤?不应该不一样吗?显然我没有正确理解这一点,所以有人可以向我解释一下吗?

提前致谢!

注意:这是 mysql 5.5.56,table 是 InnoDB。

1) 是的,它按 customers.store_id 过滤,然后根据 customer_id.

向后加入发货 table

您可以通过将 shipments(customer_id) 的索引替换为 shipments(customer_id, date) 来改进这一点,除非该索引已经涵盖了这两个字段。

2)因为是根据指标统计的估计,主要是各个指标的基数。

这不是真正的 LEFT 加入,因为您需要 store_id = 100。那不会改变性能;优化器已经想通了。 (它确实有助于读者弄清楚查询的意图。)

你说SELECT *。如果您不需要所有列,请不要要求所有列。如果有一个很大的 TEXT 列,则文本位于 "off-record" 块中,这需要努力获取。

INDEX(customer_id), INDEX(date) 不如 "composite" INDEX(customer_id, date) 这样,它可以专注于该客户的条目,并扫描所需的日期。这可能会提高速度。注意:该索引中列的顺序很重要——将 = 列 (customer_id) 首先,范围 (date >=...) 最后。

(Q1) MySQL 不会(极少数例外)一次使用多个索引。您正在过滤 shipments 两件事:customer_iddate,而不仅仅是 date。另一方面,此查询将使用 INDEX(date),并且 而不是 使用上面的复合索引:SELECT * FROM shipments where date >= CURDATE();(获取所有货物的所有信息,因此今天到所有客户。

旁注:您在两端都包括了午夜。将最后一个比较从 <= 更改为 <.

(Q2) EXPLAIN 中的数字是估计值。它们基于不一定非常精确的 "statistics" 和 "probes"。此外,在某些情况下会忽略一些提示。一个明显的遗漏是 LIMIT.

小心使用 USE INDEXFORCE INDEX。如果您觉得需要这样,您可能会遗漏一些重要的东西。如果您确实使用了它,“它今天可能会有所帮助,但明天当数据分布发生变化时,情况会变得更糟。

提示:对于与 DATE / DATETIME / DATETIME(1) / TIMESTAMP 的比较,午夜时间可以省略 'time' 部分:'2020-05-01' 与 [=32 相同=]

5.5 版?那很老了。 5.6 添加了 EXPLAIN FORMAT=JSON,这将提供更多信息——关于索引使用、排序、query_cost 等的详细信息

"This optimization stuff is still pretty obscure to me." -- 是的。 MySQL 有一个更简单的优化器。