8.2.1.1-WHERE条件优化

本章节讨论针对where条件处理的优化错误。相关例子使用了SELECT语句,但是优化措施也同样适用于DELETE和UPDATE语句中的WHERE条件。 因为MySQL优化器在不断更新,所以不是所有MySQL的优化措施都列在这里。

为了是算术运算更快,你可能会牺牲可读性来重写查询。因为MySQL会自动执行类似操作,可通常不需要这样做,这样你的查询语句的可读性和可维护性会更好。MySQL执行的一些优化措施如下:

  • 去除不必要的括号:
  ((a AND B) AND c OR ((( a AND B) AND  (c AND d))))
-> (a AND b AND c) OR (a AND b AND c AND d)
  • 常数合并:
    (a<b AND b==c) AND a=5
> b>5 AND b=c AND a=5
  • 常数条件去除:
    (b>=5 and b=5) or (b=6 and 5=5) or (b=7 and 5=6)
> b=5 or b=6

在MySQL8.0.14版本及以后的版本中,这发生在预处理阶段而不是优化阶段,这简化了连接查询。想了解更多信息和例子,见8.2.1.9 “Outer Join Optimization”。

  • 索引使用的常数表达式只被评估一次。
  • 从MySQL8.0.16开始,数值类型列与常数值进行比较将会被检查,然后无效或超出范围的值会被折叠或去除。
# create table t (c tinyint unsigned not null);
    select * from t where c << 256;
->> select * from t where 1;

想了解更多信息,见8.2.1.14 “Constant-Folding Optimization”。

  • 对于MyISAM存储引擎和MEMORY存储引擎来说,对单表进行没有where条件的count(*),会直接从表信息中获取数据。这种优化在使用NOT NULL的单表查询时也同样适用。
  • 及早检测不符合条件的常数表达式。MySQL会很快检测到付可能满足的条件,并直接返回。
  • 如果你没有使用group by或统计函数(如count(),min()等),having会和where语句合并。
  • 对于join查询中的每一张表,构造更简单的where条件,这样可以加快where条件的评估速度,尽快跳过不符合条件的行。
  • 所有常量表会被先读取。常量表是指符合以下条件之一的表:
    • 空表或只有一条记录的表。
    • where条件中使用主键或唯一索引来查询的表,所有索引部分都会和常数值来比较。 下面的所有表都会被当成常量表:
    select * from t where primary_key=1;
    select * from t1,t2;
      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条件的会被跳过。 下面是一些执行很快的查询的例子:
select count(*) from tbl_name;

select min(key_part1),max(key_part1) from tbl_name;

select max(key_part2) from tbl_name where key_part1=constant;

select ... from tbl_name order by key_part1,key_part2,... limit 10;

select ... from tbl_name order by key_part1 desc,key_part2 desc,... limit 10;

MySQL只使用索引来处理查询,假设所有索引上的列都是数字的话:

select key_part1,key_part2 from tbl_name where key_part1=va;

select count(*) from tbl_name where key_part1=val1 and key_part2=val2;

select max(key_part2) from tbl_name group by key_part1;

下面的的查询会使用索引来获取排序号的记录,而不需要再进行单独的排序:

select ... from tbl_name
    order by key_part1,key_part2,...;

select ... from tbl_name
    order by key_part1 desc,key_part2 desc,...;