选择 * 和查询优化器

Posted

技术标签:

【中文标题】选择 * 和查询优化器【英文标题】:Select * and the query optimizer 【发布时间】:2012-07-20 05:28:06 【问题描述】:

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

无论如何,我已经创建了一个这样的表:

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

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

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 |       |
+----+-------------+------------+------+---------------+------+---------+------+------+-------+

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

【问题讨论】:

当您从表中选择所有内容时,您为什么关心使用什么索引。如果您未指定订单,请不要依赖返回的订单 我并不关心使用哪个索引,只要使用索引即可。但是我确实关心优化,所以如果一个索引比另一个索引给我更好的结果,我宁愿使用更好的索引,从我所做的阅读来看,据说主键索引比时间索引更好,所以我没有'不明白为什么优化器没有使用它。 【参考方案1】:

正如 Gordon 所说,您不应该依赖结果集的自然顺序。您得到结果的原因如下:

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

在第二种情况下,userID 列不属于任何索引,MySQL 必须执行常规表扫描才能获取结果。 "use index(idx_time)" 在这种情况下什么也不做,因为没有 WHERE 子句使用time 列。

编辑: 它仅在有选择时适用,但如果无法使用USE INDEX 中指定的索引,MySQL 将不会使用该表上的任何索引 进行搜索(WHERE/ON 子句)并将读取整张桌子。所以在使用索引提示时要非常小心。explain 中 type='index' 的行也意味着将读取表中的所有行,并且几乎与 type='ALL' 一样糟糕。

您应该查看index hints 和explain output 上的 MySQL 手册。

【讨论】:

啊,明白了!现在这是有道理的。所以除此之外,使用“使用索引(foo)”真的只适用于mysql在索引之间进行选择的情况吗?从本质上讲,它让程序员在 indexex 之间做出决定,而不是让优化器来决定?【参考方案2】:

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

如果您希望结果集排序,则包括:

order by time desc

这是您可以依赖排序的唯一方法。

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

【讨论】:

以上是关于选择 * 和查询优化器的主要内容,如果未能解决你的问题,请参考以下文章

mysql查询优化器应该怎么使用

DBA的五款最佳SQL查询优化工具

MySql性能优化查询优化

mysql中主查询和子查询关系是啥?

那个mysql 子查询和连接查询 一般常用哪个 谁效率高些

提高sql server查询优化器结果的方法