MySQL 8.0 官方文档 第八章 优化(十九)—— DISTINCT优化 和 LIMIT 查询优化

目录

  • 第八章 优化(十九)—— DISTINCT优化 和 LIMIT 查询优化

    • 8.2 优化SQL语句
      • 8.2.1 优化 SELECT 语句
        • 8.2.1.18 DISTINCT 优化
        • 8.2.1.19 LIMIT 查询优化

第八章 优化(十九)—— DISTINCT优化 和 LIMIT 查询优化

8.2 优化SQL语句

8.2.1 优化 SELECT 语句

8.2.1.18 DISTINCT 优化

在许多情况下,DISTINCTORDER 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;

8.2.1.19 LIMIT 查询优化

如果只需要结果集中指定数量的行,而不是获取整个结果集和丢弃额外的数据,则请在查询中使用LIMIT子句。

MySQL有时会优化有LIMIT row_count子句而没有HAVING子句的查询:(译者:row_count表示行数量,例如,LIMIT 10)

  • 如果使用LIMIT只选择有限的几行,则MySQL会在某些情况下使用索引,而通常它倾向于执行完整的全表扫描。

  • 如果将LIMIT row_countORDER BY结合使用,那么,MySQL在找到排序结果中前面的row_count行时,就会立刻停止排序,而不会对整个结果进行排序。如果排序是通过使用索引来完成的,那么这是非常快的。如果必须进行文件排序(filesort),那么在找到前面的row_count行之前,将选择不带LIMIT子句的所有匹配查询的行,并对它们中的大多数或全部行进行排序。在找到最初的行集之后,MySQL不会对结果集的任何剩余部分进行排序。

    这种行为的一种表现是:携带LIMIT和不带LIMITORDER BY查询可能以不同的顺序返回行,如本节稍后所述。

  • 如果LIMIT row_countDISTINCT结合使用,则,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,因此携带和不带LIMITORDER 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 BYGROUP BYLIMIT子句的查询,优化器会默认尝试选择有序索引,因为这样做将加快查询执行。在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 官方文档 第八章 优化(二十)—— 函数调用优化

你可能感兴趣的:(MySQL,8.0,MySQL优化)