本节讨论了在处理 WHERE 子句时可以进行的优化。这些例子使用了 SELECT 语句,但是同样的优化也适用于 DELETE 和 UPDATE 语句中的 WHERE 子句。

    注意事项:由于 MySQL 优化器的工作正在进行中,这里没有记录 MySQL 执行的所有优化。

    你可能想重写你的查询,使算术运算更快,而牺牲可读性。因为 MySQL 会自动进行类似的优化,所以你通常可以 避免 这项工作,并将查询留在一个更易理解和可维护的形式中。由 MySQL 执行的一些优化如下:

    • 删除不必要的括号

      1. ((a AND b) AND c OR (((a AND b) AND (c AND d))))
      2. -> (a AND b AND c) OR (a AND b AND c AND d)
    • 恒定折叠

      1. (a<b AND b=c) AND a=5
      2. -> b>5 AND b=c AND a=5
    • 恒定条件去除

      1. (b>=5 AND b=5) OR (b=6 AND 5=5) OR (b=7 AND 5=6)
      2. -> b=5 OR b=6

      在 MySQL 8.0.14 及以后的版本中,这发生在准备阶段而不是优化阶段,这有助于简化连接。参见第 8.2.1.9 节 外连接优化,以了解更多信息和实例。

    • 索引使用的常量表达式只被评估一次。

    • 从 MySQL 8.0.16 开始,数字类型的列与常量值的比较会被检查,并折叠或删除无效或超限的值。

      1. # CREATE TABLE t (c TINYINT UNSIGNED NOT NULL);
      2. SELECT * FROM t WHERE c 256;
      3. -≫ SELECT * FROM t WHERE 1;

      更多信息见第 8.2.1.14 节,恒定折叠优化

    • 在没有 WHERE 的单个表上的 [COUNT(*)](https://dev.mysql.com/doc/refman/8.0/en/aggregate-functions.html#function_count)直接从 MyISAM 表和 MEMORY 表的信息中检索出来。当只对一个表使用时,对任何 NOT NULL 表达式也会这样做。

    • 早期检测无效的常量表达式。MySQL 快速检测到一些 SELECT 语句是不可能的,并且不返回记录。

    • 如果不使用 GROUP BY 或聚合函数([COUNT()](https://dev.mysql.com/doc/refman/8.0/en/aggregate-functions.html#function_count)[MIN()](https://dev.mysql.com/doc/refman/8.0/en/aggregate-functions.html#function_min)等),HAVING 将与 WHERE 合并。

    • 对于连接中的每一个表,都会构造一个更简单的 WHERE,以获得对表的快速 WHERE 评估,也可以尽快跳过记录。

    • 所有的常量表都会在查询中的任何其他表之前被首先读取。一个常量表是以下任何一种。

      • 一个空表或一个只有一条记录的表。
      • 一个与 PRIMARY KEY 上的 WHERE 子句或 UNIQUE 索引一起使用的表,其中所有的索引部分都与常量表达式相比较,并被定义为 NOT NULL。

    以下所有的表都被作为常量表使用:

    1. SELECT * FROM t WHERE primary_key=1;
    2. SELECT * FROM t1,t2
    3. WHERE t1.primary_key=1 AND t2.primary_key=t1.id;
    • 连接表的最佳连接组合是通过尝试所有的可能性而找到的。如果 ORDER BY 和 GROUP BY 子句中的所有列都来自于同一个表,那么在连接时首先选择该表。

    • 如果有一个 ORDER BY 子句和一个不同的 GROUP BY 子句,或者 ORDER BY 或 GROUP BY 包含来自连接队列中第一个表以外的表的列,将创建一个临时表。

    • 如果你使用 SQL_SMALL_RESULT 修改器,MySQL 会使用一个内存中的临时表。

    • 每个表的索引都被查询,并且使用最佳索引,除非优化器认为使用表扫描更有效率。曾几何时,扫描的使用是基于最佳索引是否跨越了表的 30% 以上,但一个固定的百分比不再决定使用索引或扫描的选择。现在的优化器更加复杂,它基于额外的因素进行估计,如表的大小、行的数量和 I/O 块的大小。

    • 在某些情况下,MySQL 可以从索引中读取行,甚至不需要查阅数据文件。如果从索引中使用的所有列都是数字,则只使用索引树来解决查询。

    • 在每条记录被输出之前,那些不符合 HAVING 子句的记录被跳过。

    一些查询速度非常快的例子:

    1. SELECT COUNT(*) FROM tbl_name;
    2. -- kye_ 前缀表示使用的是索引列
    3. SELECT MIN(key_part1),MAX(key_part1) FROM tbl_name;
    4. SELECT MAX(key_part2) FROM tbl_name
    5. WHERE key_part1=constant; -- constant 常量
    6. SELECT ... FROM tbl_name
    7. ORDER BY key_part1,key_part2,... LIMIT 10;
    8. SELECT ... FROM tbl_name
    9. ORDER BY key_part1 DESC, key_part2 DESC, ... LIMIT 10;

    假设被索引的列是数字的,MySQL 只用索引树来解决以下查询:

    1. SELECT key_part1,key_part2 FROM tbl_name WHERE key_part1=val;
    2. SELECT COUNT(*) FROM tbl_name
    3. WHERE key_part1=val1 AND key_part2=val2;
    4. SELECT MAX(key_part2) FROM tbl_name GROUP BY key_part1;

    下面的查询使用索引来检索排序后的行,不需要单独的排序过程:

    1. SELECT ... FROM tbl_name
    2. ORDER BY key_part1,key_part2,... ;
    3. SELECT ... FROM tbl_name
    4. ORDER BY key_part1 DESC, key_part2 DESC, ... ;