选择*和查询优化器

时间:2012-07-20 22:33:13

标签: mysql database select optimization

我有一个非常简单的MySQL表,有一些非常奇怪的行为。顺便说一下,奇怪的行为正是我想要它做的,但我不想把它投入生产,不知道它为什么要这样做。

无论如何,我有一个像这样的表:

Create table `raceTimes` (
    `userID` mediumint(8) unsigned,
    `time` time,
    primary key (`userID`),
    key `idx_time` (`time`)
) engine=InnoDB default charset=utf8;

现在,当我从raceTimes查询中执行Select *时,我得到如下结果集:

mysql> Select * from raceTimes;
+--------+----------+
| userID | time     |
+--------+----------+
|     14 | 12:37:46 |
|      6 | 12:41:11 |
|      5 | 12:48:45 |
|     13 | 12:55:46 |
|     10 | 13:13:37 |
|      9 | 13:40:37 |
|     17 | 15:30:44 |
|     18 | 15:46:58 |
|      3 | 16:16:45 |
|      8 | 16:40:11 |
|      7 | 16:41:11 |
|      4 | 16:48:45 |
|     16 | 20:30:44 |
|     15 | 20:37:44 |
|      1 | 21:00:00 |
|      2 | 21:16:00 |
|     11 | 23:13:37 |
|     20 | 23:14:58 |
|     19 | 23:46:58 |
|     12 | 23:55:46 |
+--------+----------+

请注意,结果集是基于时间的顺序,从最低到最高。好吧,所以这正是我想要表格所做的事情,因为我试图将它用于游戏中的排行榜。当我对我的查询运行解释时,我得到了这个:

mysql> explain select * from raceTimes;
+----+-------------+------------+-------+---------------+----------+---------+------+------+-------------+
| id | select_type | table      | type  | possible_keys | key      | key_len | ref  | rows | Extra       |
+----+-------------+------------+-------+---------------+----------+---------+------+------+-------------+
|  1 | SIMPLE      | raceTimes  | index | NULL          | idx_time | 4       | NULL |   20 | Using index |
+----+-------------+------------+-------+---------------+----------+---------+------+------+-------------+

到目前为止一切都很棒。由于idx_time索引被排序(因为索引是),我得到了一个排序结果集,为此我正在点击一个索引。现在为了奇怪的行为。

根据我的阅读,主键默认为索引,在查询表时应该是最快的索引。然而它没被使用。我的猜测是idx_time索引小于主键索引,因为它是一个时间类型而不是mediumint(8)类型。但这只是猜测。

现在,如果我创建一个与上面创建的表相同的表,但省略了主键,如下所示:

Create table `raceTimes2` (
    `userID` mediumint(8) unsigned,
    `time` time,
    key `idx_time` (`time`)
) engine=InnoDB default charset=utf8;

然后结果集不会在此时间列上排序。即使我告诉它在我的查询中专门使用idx_time索引,这种行为仍然存在。另外,如果我对查询做了解释,我会得到这个:

mysql> explain select * from testTable6 use index(`idx_time`);
+----+-------------+------------+------+---------------+------+---------+------+------+-------+
| id | select_type | table      | type | possible_keys | key  | key_len | ref  | rows | Extra |
+----+-------------+------------+------+---------------+------+---------+------+------+-------+
|  1 | SIMPLE      | raceTimes2 | ALL  | NULL          | NULL | NULL    | NULL |   20 |       |
+----+-------------+------------+------+---------------+------+---------+------+------+-------+

所以我想要找出的是幕后发生的事情。为什么看起来如果我有一个主键和另一个索引,我可以得到一个在索引上排序的结果集,甚至没有尝试,为什么查询优化器会使用其他索引而不是主键索引?

2 个答案:

答案 0 :(得分:2)

除非在查询中包含“order by”语句,否则不应依赖结果集的顺序! SQL显式不保证顺序。不要从一个小例子推广到大型桌子上会发生什么。

如果您想要排序结果集,请包括:

order by time desc

这是您可以依赖订购的唯一方式。

例如,您可能正在进行全表扫描,并且页面缓存中已有一些页面。这些 - 以随机顺序 - 可以先读取。或者,您可以使用多个“主轴”(这是SQL Server术语)读取表,以任意顺序返回结果。

答案 1 :(得分:2)

正如戈登所说,你不应该依赖于结果集的自然顺序。您获得结果的原因如下:

在第一种情况下,MySQL仅使用idx_time索引执行查询,而不打开实际的表。当您使用的所有列都在索引中时,这是可能的(InnoDB表的主键始终附加到每个索引的末尾,因此您的索引是实际的(timeuserID)在幕后)。结果按时间排序,因为这是time索引中的实际顺序。

在第二种情况下,列userID不是任何索引的一部分,MySQL必须进行常规表扫描才能获取结果。 “use index(idx_time)”在这种情况下不执行任何操作,因为没有使用time列的WHERE子句。

编辑:
它只适用于有选择的情况,但如果不能使用USE INDEX中指定的索引,那么MySQL 将不会在该表上使用任何索引进行搜索(WHERE / ON子句)和将阅读整个表格。因此在使用索引提示时应该非常小心 同样,explain中的一行,其中type ='index'意味着表中的所有行都将被读取,并且几乎与type ='ALL'一样糟糕。

您应该查看index hintsexplain output上的MySQL手册。