在许多情况下,DISTINCT
与ORDER BY
组合使用需要一个临时表。
因为DISTINCT
可能使用到GROUP BY
,所以要学习MySQL如何处理ORDER BY
中的列,或如何处理含有不属于SELECT(选择)列表中的列的HAVING
子句。参见12.20.3节,“MySQL处理分组”。
在大多数情况下,DISTINCT
子句可视为GROUP BY
的特例。例如,以下两个查询是等效的:
SELECT DISTINCT c1, c2, c3 FROM t1
WHERE c1 > const;
SELECT c1, c2, c3 FROM t1
WHERE c1 > const GROUP BY c1, c2, c3;
正是由于这种等效性,所以,适用于GROUP BY
查询的优化也可以应用于具有DISTINCT
子句的查询。因此,关于DISTINCT
查询的优化可能性的更多细节,请参见8.2.1.17节“分组优化”。
当将LIMIT row_count
(限制 行数)和DISTINCT
联合使用时,MySQL会在找到指定的不同行的行数时候立刻停止。
如果没有使用到查询中指定的所有表中的全部列,MySQL会在找到第一个匹配表后立即停止扫描任何未使用的表。在下面的情况中,假设表t1在表t2之前使用(您可以用EXPLAIN检查),当MySQL找到表t2中的第一行时,它停止从表t2读取(对于t1中的任何特定行):
SELECT DISTINCT t1.a FROM t1, t2 where t1.a = t2.a;
如果只需要结果集中指定数量的行,而不是获取整个结果集和丢弃额外的数据,则请在查询中使用LIMIT子句。
MySQL有时会优化有LIMIT row_count
子句而没有HAVING
子句的查询:(译者:row_count表示行数量,例如,LIMIT 10)
如果使用LIMIT
只选择有限的几行,则MySQL会在某些情况下使用索引,而通常它倾向于执行完整的全表扫描。
如果将LIMIT row_count
和ORDER BY
结合使用,那么,MySQL在找到排序结果中前面的row_count行时,就会立刻停止排序,而不会对整个结果进行排序。如果排序是通过使用索引来完成的,那么这是非常快的。如果必须进行文件排序(filesort),那么在找到前面的row_count行之前,将选择不带LIMIT
子句的所有匹配查询的行,并对它们中的大多数或全部行进行排序。在找到最初的行集之后,MySQL不会对结果集的任何剩余部分进行排序。
这种行为的一种表现是:携带LIMIT
和不带LIMIT
的ORDER BY
查询可能以不同的顺序返回行,如本节稍后所述。
如果LIMIT row_count
与DISTINCT
结合使用,则,MySQL在找到唯一的row_count行时立即停止。
在某些情况下,解析GROUP BY
子句,可以通过按顺序读取索引(或对索引进行排序),然后计算摘要直到索引值发生变化。在这种情况下,LIMIT row_count
不会计算任何不必要的GROUP BY
值。
一旦MySQL向客户端发送了所需的行数,它就会中止查询,除非您使用SQL_CALC_FOUND_ROWS
(SQL计算找到的行)。在这种情况下,可以使用SELECT FOUND_ROWS()
来检索该数量的行。见12.16节,“信息函数”。
使用LIMIT 0
会快速返回一个空集。这对于检查查询的有效性非常有用。它还可以用于在使用MySQL API的应用程序中获取结果列的类型,该API可得到结果集元数据。在mysql客户机程序中,可以使用--column type info
选项来显示结果列类型。
如果服务器使用临时表来解析查询,它将使用LIMIT row_count
子句来计算需要多少空间。
如果没有索引能用于ORDER BY
,但同时存在LIMIT
子句,则优化器可以避免执行合并文件,并利用内存中文件排序操作在内存中对行集进行排序。
如果在ORDER BY
列中多行的值相同,则服务器可以任意地以任何顺序返回这些行,并且根据总体执行计划的不同,可能会以不同的方式执行该操作。换句话说,相对于非排序列,这些行的排序顺序是不确定的。
影响执行计划的一个因素是LIMIT
,因此携带和不带LIMIT
的ORDER BY
查询可能以不同的顺序返回结果行。考虑以下查询,它是按category列排序的,与id和rating列顺序无关:
mysql> SELECT * FROM ratings ORDER BY category;
+----+----------+--------+
| id | category | rating |
+----+----------+--------+
| 1 | 1 | 4.5 |
| 5 | 1 | 3.2 |
| 3 | 2 | 3.7 |
| 4 | 2 | 3.5 |
| 6 | 2 | 3.5 |
| 2 | 3 | 5.0 |
| 7 | 3 | 2.7 |
+----+----------+--------+
包含LIMIT
可能会影响每个category值中的行顺序。例如,这是一个有效的查询结果:(译者:注意返回结果行中的行位置的变化。)
mysql> SELECT * FROM ratings ORDER BY category LIMIT 5;
+----+----------+--------+
| id | category | rating |
+----+----------+--------+
| 1 | 1 | 4.5 |
| 5 | 1 | 3.2 |
| 4 | 2 | 3.5 |
| 3 | 2 | 3.7 |
| 6 | 2 | 3.5 |
+----+----------+--------+
在每种情况下,结果行集要按ORDER BY
列进行排序,这就是SQL标准要求的全部内容。
如果要确保不管有无带LIMIT
,返回的行顺序相同很重要,那么在ORDER BY
子句中包含其他列以使顺序具有确定性。例如,如果id值是唯一的,则可以通过如下排序使给定category值的行按id顺序显示:
mysql> SELECT * FROM ratings ORDER BY category, id;
+----+----------+--------+
| id | category | rating |
+----+----------+--------+
| 1 | 1 | 4.5 |
| 5 | 1 | 3.2 |
| 3 | 2 | 3.7 |
| 4 | 2 | 3.5 |
| 6 | 2 | 3.5 |
| 2 | 3 | 5.0 |
| 7 | 3 | 2.7 |
+----+----------+--------+
mysql> SELECT * FROM ratings ORDER BY category, id LIMIT 5;
+----+----------+--------+
| id | category | rating |
+----+----------+--------+
| 1 | 1 | 4.5 |
| 5 | 1 | 3.2 |
| 3 | 2 | 3.7 |
| 4 | 2 | 3.5 |
| 6 | 2 | 3.5 |
+----+----------+--------+
对于带有ORDER BY
或GROUP BY
和LIMIT
子句的查询,优化器会默认尝试选择有序索引,因为这样做将加快查询执行。在MySQL 8.0.21之前,没有办法否决这种行为,即使在使用一些其他优化可能更快的情况下也是如此。从MySQL 8.0.21开始,可以通过将optimizer_switch系统变量的prefer_ordering_index
(首选排序索引)标记设置为off来关闭这种优化。
示例: 首先,我们创建并填充一个表t,如下所示:
# Create and populate a table t:
mysql> CREATE TABLE t (
-> id1 BIGINT NOT NULL,
-> id2 BIGINT NOT NULL,
-> c1 VARCHAR(50) NOT NULL,
-> c2 VARCHAR(50) NOT NULL,
-> PRIMARY KEY (id1),
-> INDEX i (id2, c1)
-> );
# [Insert some rows into table t - not shown] 向表t中插入一些行在此不显示
验证该prefer_ordering_index
标志是启用的:
mysql> SELECT @@optimizer_switch LIKE '%prefer_ordering_index=on%';
+------------------------------------------------------+
| @@optimizer_switch LIKE '%prefer_ordering_index=on%' |
+------------------------------------------------------+
| 1 |
+------------------------------------------------------+
由于下面的查询有一个LIMIT
子句,如果可能的话,我们希望它使用有序索引。在本例中,正如我们从EXPLAIN输出中看到的,它使用了表的主键。
mysql> EXPLAIN SELECT c2 FROM t
-> WHERE id2 > 3
-> ORDER BY id1 ASC LIMIT 2\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: t
partitions: NULL
type: index
possible_keys: i
key: PRIMARY
key_len: 8
ref: NULL
rows: 2
filtered: 70.00
Extra: Using where
现在我们禁用prefer_ordering_index
标志,并重新运行相同的查询;这次它使用索引 i(该索引包括WHERE子句中使用的id2列)和文件排序:
mysql> SET optimizer_switch = "prefer_ordering_index=off";
mysql> EXPLAIN SELECT c2 FROM t
-> WHERE id2 > 3
-> ORDER BY id1 ASC LIMIT 2\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: t
partitions: NULL
type: range
possible_keys: i
key: i
key_len: 8
ref: NULL
rows: 14
filtered: 100.00
Extra: Using index condition; Using filesort
另请参见第8.9.2节“可切换优化”。
上一集 MySQL 8.0 官方文档 第八章 优化(十八)—— 分组优化
下一集 MySQL 8.0 官方文档 第八章 优化(二十)—— 函数调用优化