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

时间:2009-06-04 16:17:40

标签: mysql select indexing

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)

但是这样,它被使用了:

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)

此外,这个也没有使用索引:

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)

6 个答案:

答案 0 :(得分:3)

SELECT  *
FROM   users
WHERE  imtype != '0' and robotno is null

单个连续的(imtype, robotno)范围不满足此条件。

如果你有这样的记录:

imtype  robotno

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

,按(imtype, robotno)排序,然后会返回记录157,而其他记录则不会。

您需要创建此索引以满足条件:

CREATE INDEX ix_users_ri ON users (robotno, imptype)

并稍微重写您的查询:

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

,这将产生两个连续的块:

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

此索引也将提供此查询:

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

,由于同样的原因,现在没有任何索引提供。

实际上,此查询的索引:

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

仅用于imtype上的粗略过滤(using where字段中的注释extra),它不在robotno的范围内

答案 1 :(得分:2)

您需要一个将robotno作为第一列的索引。您现有的索引是(imtype,robotno)。由于imtype不在where子句中,因此无法使用该索引。

(robotno,imtype)上的索引可以用于where子句中只有robotno的查询,也可以用于where子句中同时包含imtype和robotno的查询(但不是imtype本身)。

查看how MySQL uses indexes上的文档,查找有关多列索引和“最左前缀”的部分。

答案 2 :(得分:1)

顺便说一句,如果您认为自己比优化器更了解(通常是这种情况),您可以通过附加强制MySQL使用特定索引

FORCE INDEX (index_name)之后

FROM users

答案 3 :(得分:0)

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

答案 4 :(得分:0)

数据库系统查询计划程序通过分析查询的where子句相对于索引的 selective 来确定是否执行索引扫描。 (索引也用于将表连接在一起,但此处只有users。)

第一个查询有where imtype != '0'。这将选择users中的几乎所有行,假设您有大量不同的imtype值。不等式运算符本质上是无选择性的。所以MySQL查询规划器在这里打赌,通过索引读取将无济于事,并且它可能只是对整个表进行顺序扫描,因为它可能必须这样做。

另一方面,如果你说where imtype ='0',等式是一个高度选择性的运算符,MySQL会认为通过只读取几个索引块就可以避免读取几乎所有{{1}的块。表本身。所以它会选择索引。

在你的第二个例子users中,MySQL知道索引具有高度选择性(尽管只有where imtype in ('1','2')的一半选择性),并且它再次打赌使用索引会导致正如你所发现的那样,收益很大。

在你的第三个例子where imtype = '0'中,MySQL可能无法有效地使用where robotno=1上的索引,因为它需要读入所有索引块才能找到users(imtype,robotno)记录号:索引首先按robotno=1排序,然后按imtype排序。如果你在robotno上有另一个索引,那么MySQL会热切地使用它。

作为一个脚注,如果您有两个索引,一个在users(robotno)上,另一个在users(imtype)上,并且您的查询位于users(imtype,robotno),则任一索引都会使您的查询变快,但MySQL可能会选择where imtype = '0',因为它更紧凑,需要从中读取更少的块。

我在这里非常简单。早期的数据库系统只会查看imtype的数据类型,并对查询的选择性进行粗略的猜测,但人们很快意识到给查询规划器提供了有趣的事实,例如表的总大小,每列中的ditinct值的数量等等将使它能够做出更明智的决定。例如,如果您有一个users(imtype)表,其中users只是每个'0'或'1',那么查询计划程序可能会选择索引,因为在这种情况下imtype更具选择性

看看MySQL UPDATE STATISTICS语句,你会发现它的查询规划器必须是复杂的。出于这个原因,在使用FORCE语句来指示查询计划之前,我会犹豫不决。相反,使用UPDATE STATISTICS为查询计划程序提供改进的信息,以作出决定。

答案 5 :(得分:0)

您的索引超过users(imtype,robotno)。要使用此索引,必须使用imtypeimtyperobotno来限定行。您只是在查询中使用robotno,因此无法使用此索引。