首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >关于MySQL最左前缀匹配优化

关于MySQL最左前缀匹配优化
EN

Stack Overflow用户
提问于 2021-11-11 15:40:48
回答 2查看 153关注 0票数 0

我现在有一个这样的桌子:

代码语言:javascript
复制
> DESC userInfo;
+--------+---------------------+------+-----+---------+----------------+
| Field  | Type                | Null | Key | Default | Extra          |
+--------+---------------------+------+-----+---------+----------------+
| id     | int(11)             | NO   | PRI | NULL    | auto_increment |
| name   | char(32)            | NO   | MUL | NULL    |                |
| age    | tinyint(3) unsigned | NO   |     | NULL    |                |
| gender | tinyint(1)          | NO   |     | 1       |                |
+--------+---------------------+------+-----+---------+----------------+

I使(名称、年龄)成为一个联合的唯一索引:

代码语言:javascript
复制
> SHOW INDEX FROM userInfo;
+----------+------------+--------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+--------------------+
| Table    | Non_unique | Key_name           | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment      |
+----------+------------+--------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+--------------------+
| userInfo |          0 | PRIMARY            |            1 | id          | A         |           0 |     NULL | NULL   |      | BTREE      |         |                    |
| userInfo |          0 | joint_unique_index |            1 | name        | A         |           0 |     NULL | NULL   |      | BTREE      |         | 联合唯一索引       |
| userInfo |          0 | joint_unique_index |            2 | age         | A         |           0 |     NULL | NULL   |      | BTREE      |         | 联合唯一索引       |
+----------+------------+--------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+--------------------+
3 rows in set (0.00 sec)

现在,当我使用以下查询语句时,它的类型是全部:

代码语言:javascript
复制
> DESC SELECT * FROM userInfo WHERE age = 18;
+----+-------------+----------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table    | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra       |
+----+-------------+----------+------------+------+---------------+------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | userInfo | NULL       | ALL  | NULL          | NULL | NULL    | NULL |    1 |   100.00 | Using where |
+----+-------------+----------+------------+------+---------------+------+---------+------+------+----------+-------------+

我可以理解这种行为,因为根据最左边的前缀匹配特性,在查询时,年龄将不被用作索引列。

但是,当我使用以下语句查询时,它的类型是Index:

代码语言:javascript
复制
> DESC SELECT name, age FROM userInfo WHERE age = 18;
+----+-------------+----------+------------+-------+---------------+--------------------+---------+------+------+----------+--------------------------+
| id | select_type | table    | partitions | type  | possible_keys | key                | key_len | ref  | rows | filtered | Extra                    |
+----+-------------+----------+------------+-------+---------------+--------------------+---------+------+------+----------+--------------------------+
|  1 | SIMPLE      | userInfo | NULL       | index | NULL          | joint_unique_index | 132     | NULL |    1 |   100.00 | Using where; Using index |
+----+-------------+----------+------------+-------+---------------+--------------------+---------+------+------+----------+--------------------------+
1 row in set, 1 warning (0.00 sec)

我不明白这个结果是怎么产生的。根据示例1,作为查询条件的年龄并不满足最左边的前缀匹配功能,但从结果来看,它的类型实际上是Index!这是MySQL中的优化吗?

当我试图确保使用索引列作为查询条件时,它们的类型始终是ref,如下所示:

代码语言:javascript
复制
> DESC SELECT * FROM userInfo WHERE name = "Jack";
+----+-------------+----------+------------+------+--------------------+--------------------+---------+-------+------+----------+-------+
| id | select_type | table    | partitions | type | possible_keys      | key                | key_len | ref   | rows | filtered | Extra |
+----+-------------+----------+------------+------+--------------------+--------------------+---------+-------+------+----------+-------+
|  1 | SIMPLE      | userInfo | NULL       | ref  | joint_unique_index | joint_unique_index | 128     | const |    1 |   100.00 | NULL  |
+----+-------------+----------+------------+------+--------------------+--------------------+---------+-------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)

> DESC SELECT name, age FROM userInfo WHERE name = "Jack";
+----+-------------+----------+------------+------+--------------------+--------------------+---------+-------+------+----------+-------------+
| id | select_type | table    | partitions | type | possible_keys      | key                | key_len | ref   | rows | filtered | Extra       |
+----+-------------+----------+------------+------+--------------------+--------------------+---------+-------+------+----------+-------------+
|  1 | SIMPLE      | userInfo | NULL       | ref  | joint_unique_index | joint_unique_index | 128     | const |    1 |   100.00 | Using index |
+----+-------------+----------+------------+------+--------------------+--------------------+---------+-------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)

请告诉我为什么当我使用年龄作为查询时,第一个结果是全部,而第二个结果是索引。这是MySQL优化的结果吗?换句话说,在使用SELECT *时,不应用索引列查询,但是当使用SELECT joint_col1、joint_col2 FROM joint_col2时,则使用索引列查询(因为类型是索引)。为什么会出现这种差异呢?

EN

回答 2

Stack Overflow用户

发布于 2021-11-11 16:18:16

简化一下,索引(name, age)基本上是相同的,就像您有另一个带有这些值副本的表(name, age, id)一样。由于技术原因,(对于InnoDB)包含主键-- MySQL使用它来查找原始表中的整行。

因此,您基本上可以把它看作是有两个表:(id, name, age, gender)(name, age, id),它们都有相同的行数。如果提供最左边的列,两者都可以跳到/跳过特定的行。

如果你这样做了

代码语言:javascript
复制
SELECT * FROM userInfo WHERE age = 18;

正如您预期的那样,MySQL必须读取表的每一行,因为找不到年龄为18的行的速度更快--正如您总结的那样,没有以年龄作为最左边列的索引。

如果你这样做了

代码语言:javascript
复制
SELECT name, age FROM userInfo WHERE age = 18;

情况并没有太大变化: MySQL也必须读取每一行,而且仍然不能使用(name, age)上的索引来限制必须读取的行数。

但是MySQL可以使用一个技巧:因为您只需要列nameage,所以它可以从索引中读取所有行--“表”,并且仍然有它需要的所有信息,因为索引是覆盖索引(它覆盖所有所需的列)。

MySQL为什么要这么做?因为它必须读取更少的绝对数据,而不是读取完整的表:索引以更少的字节存储您想要的信息(因为它不包括gender)。读取较少的数据以获取所需的所有信息,比读取更多的数据以获得相同的信息更好/更快。所以MySQL就会这么做。

但是要强调这一点:您的查询仍然必须读取所有行,它基本上仍然是一个完整的表扫描(" all ") --只是在一个列较少的“表”(索引)上,以保存一些字节。虽然您不会注意到一个tinyint列的不同之处,但是如果您的表中有很多或较大的列,那么它实际上是一个相关的加速。

票数 1
EN

Stack Overflow用户

发布于 2021-11-11 17:44:21

“最左边”规则适用于WHERE子句和INDEX

INDEX(name, age)对于WHERE name = '...'WHERE name = '...' AND ((anything else))非常有用,因为name是索引中最左边的。

您拥有的是WHERE age = ... (以及其他任何东西),因此您需要INDEX(age) (或INDEX(age, ...))。

特别是,SELECT name, age FROM userInfo WHERE age = 18;

代码语言:javascript
复制
INDEX(age) -- good
INDEX(age, name) -- better because it is "covering".

WHERE中列的顺序无关紧要;INDEX中的顺序很重要。

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/69931047

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档