简体   繁体   English

为什么执行此查询时MySQL不使用索引?

[英]Why does MySQL not use an index when executing this query?

mysql> desc users;
+-------------+------------------+------+-----+---------+----------------+
| Field       | Type             | Null | Key | Default | Extra          |
+-------------+------------------+------+-----+---------+----------------+
| id          | int(10) unsigned | NO   | PRI | NULL    | auto_increment |
| email       | varchar(128)     | NO   | UNI |         |                |
| password    | varchar(32)      | NO   |     |         |                |
| screen_name | varchar(64)      | YES  | UNI | NULL    |                |
| reputation  | int(10) unsigned | NO   |     | 0       |                |
| imtype      | varchar(1)       | YES  | MUL | 0       |                |
| last_check  | datetime         | YES  | MUL | NULL    |                |
| robotno     | int(10) unsigned | YES  |     | NULL    |                |
+-------------+------------------+------+-----+---------+----------------+
8 rows in set (0.00 sec)


mysql> create index i_users_imtype_robotno on users(imtype,robotno);
Query OK, 24 rows affected (0.25 sec)
Records: 24  Duplicates: 0  Warnings: 0
mysql> explain select * from users where imtype!='0' and robotno is null;
+----+-------------+-------+------+------------------------+------+---------+------+------+-------------+
| id | select_type | table | type | possible_keys          | key  | key_len | ref  | rows | Extra       |
+----+-------------+-------+------+------------------------+------+---------+------+------+-------------+
|  1 | SIMPLE      | users | ALL  | i_users_imtype_robotno | NULL | NULL    | NULL |   24 | Using where |
+----+-------------+-------+------+------------------------+------+---------+------+------+-------------+
1 row in set (0.00 sec)

But this way,it's used: 但是这样使用:

mysql> explain select * from users where imtype in ('1','2') and robotno is null;
+----+-------------+-------+-------+------------------------+------------------------+---------+------+------+-------------+
| id | select_type | table | type  | possible_keys          | key                    | key_len | ref  | rows | Extra       |
+----+-------------+-------+-------+------------------------+------------------------+---------+------+------+-------------+
|  1 | SIMPLE      | users | range | i_users_imtype_robotno | i_users_imtype_robotno | 11      | NULL |    3 | Using where |
+----+-------------+-------+-------+------------------------+------------------------+---------+------+------+-------------+
1 row in set (0.01 sec)

Besides,this one also did not use index: 另外,这个也没有使用索引:

mysql> explain select id,email,imtype from users where robotno=1;
+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
| id | select_type | table | type | possible_keys | key  | key_len | ref  | rows | Extra       |
+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
|  1 | SIMPLE      | users | ALL  | NULL          | NULL | NULL    | NULL |   24 | Using where |
+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
1 row in set (0.00 sec)
SELECT  *
FROM   users
WHERE  imtype != '0' and robotno is null

This condition is not satisified by a single contiguous range of (imtype, robotno) . (imtype, robotno)的单个连续范围不能满足此条件。

If you have records like this: 如果您有这样的记录:

imtype  robotno

$       NULL
$       1    
0       NULL
0       1
1       NULL
1       1
2       NULL
2       1

, ordered by (imtype, robotno) , then the records 1 , 5 and 7 would be returned, while other records wouldn't. 通过有序(imtype, robotno)则记录157将被返回,而其它的记录不会。

You'll need create this index to satisfy the condition: 您需要创建此索引来满足条件:

CREATE INDEX ix_users_ri ON users (robotno, imptype)

and rewrite your query a little: 并稍微重写一下查询:

SELECT  *
FROM   users
WHERE  (
       robotno IS NULL
       AND imtype < '0'
       )
       OR
       (
       robotno IS NULL
       AND imtype > '0'
       )

, which will result in two contiguous blocks: ,这将导致两个连续的块:

robotno imtype  

--- first block start
NULL    $
--- first block end
NULL    0
--- second block start
NULL    1
NULL    2
--- second block end
1       $
1       0
1       1
1       2

This index will also serve this query: 该索引也将用于以下查询:

SELECT id, email, imtype
FROM   users
WHERE  robotno = 1

, which is not served now by any index for the same reason. ,由于相同的原因,现在没有任何索引提供该服务。

Actually, the index for this query: 实际上,此查询的索引:

SELECT  *
FROM    users
WHERE   imtype in ('1', '2')
        AND robotno is null

is used only for coarse filtering on imtype (note using where in the extra field), it doesn't range robotno 's 仅用于粗过滤上imtype (注意using whereextra字段),它不会范围robotno

You need an index that has robotno as the first column. 您需要将robotno作为第一列的索引。 Your existing index is (imtype,robotno). 您现有的索引是(imtype,robotno)。 Since imtype is not in the where clause, it can't use that index. 由于imtype不在where子句中,因此无法使用该索引。

An index on (robotno,imtype) could be used for queries with just robotno in the where clause, and also for queries with both imtype and robotno in the where clause (but not imtype by itself). 在(robotno,imtype)上的索引可以用于where子句中仅具有robotno的查询,还可以用于where子句中具有imtype和robotno的查询(但本身不具有imtype)。

Check out the docs on how MySQL uses indexes , and look for the parts that talk about multi-column indexes and "leftmost prefix". 查看有关MySQL如何使用索引的文档,并查找有关多列索引和“最左前缀”的部分。

BTW, if you think you know better than the optimizer, which is often the case, you can force MySQL to use a specific index by appending 顺便说一句,如果您认为自己比优化器更了解(通常是这种情况),则可以通过附加MySQL来强制MySQL使用特定的索引

FORCE INDEX (index_name) after FROM users . FROM users之后的FORCE INDEX (index_name)

Your index is over users(imtype,robotno) . 您的索引超过了users(imtype,robotno) In order to use this index, either imtype or imtype and robotno must be used to qualify the rows. 为了使用该索引,必须使用imtypeimtyperobotno来限定行。 You are just using robotno in your query, thus it can't use this index. 您仅在查询中使用robotno ,因此无法使用此索引。

这是因为'robotno'可能是主键,它使用了它而不是索引。

A database systems query planner determines whether to do an index scan or not by analyzing the selectivity of the query's where clause relative to the index. 数据库系统查询计划器通过分析查询相对于索引的where子句的选择性来确定是否进行索引扫描。 (Indexes are also used to join tables together, but you only have users here.) (索引也用于将表连接在一起,但是这里只有users 。)

The first query has where imtype != '0' . 第一个查询的where imtype != '0' This would select nearly all of the rows in users , assuming you have a large number of distinct values of imtype . 这会在选择几乎所有行的users ,假设你有大量的不同值的imtype The inequality operator is inherently unselective. 不等式运算符本质上是非选择性的。 So the MySQL query planner is betting here that reading through the index won't help and that it may as well just do a sequential scan through the whole table, since it probably would have to do that anyway. 因此,MySQL查询计划员在此押注,通读索引无济于事,它最好也可以对整个表进行顺序扫描,因为无论如何它可能都必须这样做。

On the other hand, had you said where imtype ='0' , equality is a highly selective operator, and MySQL would bet that by reading just a few index blocks it could avoid reading nearly all of the blocks of the users table itself. 另一方面,如果您说过where imtype ='0' ,则where imtype ='0'是一个高度选择性的运算符,而MySQL会打赌,仅读取几个索引块就可以避免读取users表本身的几乎所有块。 So it would pick the index. 因此它将选择索引。

In your second example, where imtype in ('1','2') , MySQL knows that the index will be highly selective (though only half as selective as where imtype = '0' ), and it will again bet that using the index will lead to a big payoff, as you discovered. 在您的第二个示例中, where imtype in ('1','2') ,MySQL知道索引将具有高度选择性(尽管只有where imtype = '0'下,选择性才是索引的一半),并且它将再次使用正如您所发现的,索引将带来巨大的回报。

In your third example, where robotno=1 , MySQL probably can't effectively use the index on users(imtype,robotno) since it would need to read in all the index blocks to find the robotno=1 record numbers: the index is sorted by imtype first, then robotno . 在您的第三个示例中, where robotno=1 ,MySQL可能无法有效地对users(imtype,robotno)使用索引users(imtype,robotno)因为它需要读取所有索引块才能找到robotno=1记录号:索引已排序首先按类型imtype ,然后按robotno If you had another index on users(robotno) , MySQL would eagerly use it though. 如果您在users(robotno)上有另一个索引,MySQL会急切地使用它。

As a footnote, if you had two indexes, one on users(imtype) , and the other on users(imtype,robotno) , and your query was on where imtype = '0' , either index would make your query fast, but MySQL would probably select users(imtype) simply because it's more compact and fewer blocks would need to be read from it. 作为注脚,如果您有两个索引,一个索引在users(imtype)上,另一个索引在users(imtype,robotno) ,而您的查询位于where imtype = '0' ,则任何一个索引都可以使您的查询更快,但是MySQL可能仅仅因为它更紧凑并且需要从中读取更少的块而选择users(imtype)

I'm being very simplistic here. 我在这里很简单。 Early database systems would just look at imtype's datatype and make a very rough guess at the selectivity of your query, but people very quickly realized that giving the query planner interesting facts like the total size of the table, the number of ditinct values in each column, etc. would enable it to make much smarter decisions. 早期的数据库系统只会查看类型的数据类型,并对查询的选择性做出非常粗略的猜测,但是人们很快意识到给查询计划者一些有趣的事实,例如表的总大小,每列中不同值的数量等等将使它能够做出更明智的决策。 For instance if you had a users table where imtype was only every '0' or '1', the query planner might choose the index, since in that case the where imtype != '0' is more selective. 例如,如果您有一个users表,其中imtype仅是每个'0'或'1',则查询计划者可以选择索引,因为在这种情况下where imtype != '0'更具选择性。

Take a look at the MySQL UPDATE STATISTICS statement and you'll see that its query planner must be sophisticated. 看一下MySQL UPDATE STATISTICS语句,您会发现它的查询计划器必须很复杂。 For that reason I'd hesitate a great deal before using the FORCE statement to dictate a query plan to it. 因此,在使用FORCE语句指定查询计划之前,我会犹豫很多。 Instead, use UPDATE STATISTICS to give the query planner improved information to base its decisions on. 而是使用UPDATE STATISTICS来为查询计划者提供改进的信息,以根据其决策。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM