MySQL 是怎样运行的:基于规则的优化和EXPLAIN详解

1. 基于规则的优化

1.1 条件简化

  1. 移除不必要的括号
  2. 常量传递
  3. 对一些明显永远为 True 或 False 的表达式进行简化
  4. 表达式计算;如果某个列不是单独做完表达式的操作数,优化器不会尝试对这些表达式进行化简。例如-a<-8 或 ABS(a) > 5
  5. 查询语句中没有聚合函数以及 GROUP BY 子句时,将 HAVING 和 WHERE 子句合并。
  6. 常量表检测;查询的表中没有记录或只有一条(只适用 MEMORY 或 MyISAM 引擎);使用主键等值匹配或唯一二级索引列等值匹配。
    这两种查询的表称为常量表;首先执行常量表查询,把查询中涉及到该表的条件替换为常数。

1.2 外连接消除

WHERE 搜索条件中指定 “被驱动表的列不能为 NULL”时(空值拒绝,例如指定被驱动表的某列等于一个常数),外连接和内连接可以相互转换。

1.3 子查询

(1)在 SELECT 子句中;子查询必须为标量子查询;

(2)在 FROM 子句中;

这里是把子查询的查询结果当作一个表,称为派生表

(3)在 WHERE 或 ON 子句中;

1.4 子查询在布尔表达式中的使用

(1)使用 =、>、<、>=、<=、<>、!=、<=> 做为布尔表达式的操作符

操作数 comparison_operator (子查询)

这里的子查询只能说标量子查询(返回单个值)或行子查询(返回单行记录)。

(2)[NOT] IN / ANY / SOME / ALL;子查询中不允许有 LIMIT 语句。

子查询为列子查询(单列)或表子查询(多行多列)。

  1. 操作数 [NOT] IN (子查询);

  2. 操作数 comparison_operator ANY/SOME/ALL(子查询);

  3. [NOT] EXISTS (子查询);判断子查询的结果集中是否有记录

1.5 子查询注意事项

不允许在一条语句中增删改某个表的记录时,同时还对该表进行子查询。

1.6 子查询原始执行方式

(1)标量子查询、行子查询(原始方式

对不相关的标量子查询或行子查询,分别单独执行子查询和外层查询。
对相关的,先从外层查询获取一条记录,执行子查询,再判断 WHERE 条件是否满足。

1.7 IN 子查询优化

(1)不相关的子查询

  1. 物化表;将子查询结果保存到临时表中。(写入临时表的记录会被去重,通过对结果集所有列建立联合主键或联合唯一索引实现)
    存放在内存 MEMORY 引擎中的物化表会有哈希索引,存放在磁盘上的有 B+ 树索引(根据子查询结果集的大小判断是否大于tmp_table_sizemax_heap_table_size,从而决定放在内存还是磁盘)。
    通过索引来判断某个操作数是否存在子查询结果集中时,速度就比较快了。

  2. 物化表转连接;
    转成内连接。

(2)不进行物化的方案,直接把子查询转换为连接。

  1. 将子查询转换为半连接
    半连接是在 MySQL 内部采用的一种执行子查询的方式,多条记录匹配最终只保存一条记录。
    对 INNER JOIN 来讲,s1.key1 IN (s2.key2_v1, …) 与 s1 INNER JOIN s2 ON s1.key1 = s2.key2 相比,在内连接中对于 s1 表中的某个key1 记录,在 s2 表中可能有多条 key2 记录与之匹配,这会导致该记录被多次加入结果集。与 IN 的语义并不完全一致。

  2. 半连接的执行策略
    这里的原理是:想办法消除子查询或结果表中的重复记录
    Table pullout:子查询的查询列表处只有主键或者唯一索引列,可以直接转换为内连接,因为 s2.key2 是唯一的这样就不会 s1.key1 能够匹配多条 s2.key2 的情况。
    Duplicate Weedout:建立一个临时表,每当某条 s1 表中的记录要加入结果集时,先把该记录的主键插入临时表中,若插入成功才会真正加入。
    LooseScan:子查询的查询列表处是索引列。扫描索引,但只取键值相同的第一条记录去执行匹配操作。
    Semi-join Materialization:将子查询物化时会去重,因此也是一种方案。
    FirstMatch:先去外层循环的一条记录,到子查询中找到对应记录,找到一条就停止放入结果集,再次去外循环的记录。

(3)对相关的子查询,无法使用物化表的方法,可以使用 Table pullout、Duplicate Weedout、LooseScan、FirstMatch 等方法。

(4)半连接的适用条件

子查询是在外层查询的 WHERE 或 ON 子句中出现,必须是与 IN 组成的布尔表达式;(也不能使用 NOT IN)
外层查询其他搜索条件必须使用 AND 与 IN 子查询连接;
子查询必须是一个单一的查询,不能是由 UNION 连接起来的若干个查询;
子查询不能包含 GROUP BY、HAVING 语句或聚焦函数;

(5)不使用半连接的情况

  1. 对于不相关的子查询,会尝试将它们物化之后再参与查询。

  2. 把 IN 子查询转换为 EXISTS 查询

    outer_expr IN (SELECT inner_expr FROM … WHERE subquery_where)
    EXISTS (SELECT inner_expr FROM … WHERE subquery_where AND inner_expr = outer_expr )

    这样做的好处是,可能通过条件inner_expr = outer_expr在子查询中使用索引。

    特例:如果某个操作数值为 NULL,那么表达式的结果也为 NULL;在 WHERE 或 ON 子句是不区分 NULL 和 FALSE 的。

(6)总结

会优先转换为半连接,然后选择半连接策略中成本最低的。如果不符合条件,再判断不使用半连接情况下 2 个方法哪个成本最低。

1.8 ANY/ALL 子查询优化

如果 ANY/ALL 子查询是不相关子查询,可以进行如下转换

原表达式转换为
< ANY(SELECT inner_expr …)< (SELECT MAX(inner_expr) …)
> ANY(SELECT inner_expr …)> (SELECT MIN(inner_expr) …)
< ALL(SELECT inner_expr …)< (SELECT MIN(inner_expr) …)
< ALL(SELECT inner_expr …)> (SELECT MAX(inner_expr) …)

1.9 [NOT] EXISTS 子查询优化

如果是不相关子查询,可以先执行子查询,使用 TRUE 或 FALSE 替换即可。

对相关子查询,只能按照原始方式执行。最好可以在子查询使用索引。

1.10 派生表优化

  1. 把派生表物化;会使用延迟物化的策略,即推迟到真正使用时才进行物化。

  2. 把派生表与外层查询合并。

(1)无法与外层查询合并的情况。

派生表中包含聚合函数、DISTINCT、GROUP BY、HAVING、LIMIT、UNION、子查询包含其他子查询等时。

(2)总结:MySQL在执行时,会优先尝试把派生表与外层循环合并,如果不行,再将派生表物化。

2. EXPLAN 详解

列名描述
id在一个大的查询语句中,每个 SELECT 关键字都对应唯一一个 ID
select_type小查询在大的查询语句中扮演的角色
table表名(每个表都会生成一条记录)
type单表访问方法
ref使用索引列等值查询时,与索引列进行等值匹配的对象信息

(1)JOIN 时,出现在前面的表是驱动表,后面的是被驱动表。

(2)id 为 NULL 表明是临时表。例如 对包含 UNION 子句的查询语句,会创建临时表合并两个查询的结果。

(3)select_type 各种类型

类型描述
SIMPLE不包含 UNION 或者子查询的查询
PRIMARY包含 UNION、UNION ALL 或子查询的大查询中,最左边的查询
UNION包含 UNION、UNION ALL 的大查询中,除了最左边的之外,其余小查询的类型
UNION RESULT针对临时表的查询(使用临时表完成 UNION 查询去重的工作)
SUBQUERY包含子查询的语句不能转换为半连接形式,并且为不相关的子查询,而且查询优化器采用子查询物化的方式执行该子查询
DEPENDENT SUBQUERY子查询不能转换为半连接形式,则该子查询被转换为相关子查询的形式,该子查询的第一个 SELECT 代表的那个查询
DEPENDENT UNION包含 UNION、UNION ALL 的大查询中,各小查询都依赖外层查询,除了最左边小查询外,其余小查询的类型
DERIVED包含派生表的查询中,以物化派生表的方式执行查询,则该派生表对应的子查询的类型
MATERIALIZED包含子查询,子查询物化后与外层查询进行连接

(4)type 类型(单表访问方法)

这里不再介绍之前记录过的单表访问方法了。

类型描述
system表中只有一条记录,且该表的存储引擎的统计数据是精确的(如 MyISAM、MEMORY)
eq_ref执行连接查询时,被驱动表是通过主键或者不允许存储 NULL 值的唯一二级索引列等值匹配的方式访问的(所有索引列都必须进行等值比较),被驱动表的访问方式为 eq_ref
ref普通二级索引列与常量进行等值匹配查询某个表;执行连接查询时,被驱动表是通过普通二级索引列与驱动表中某个列进行等值匹配,被驱动表的访问方式为 ref
index_merge索引合并方式
unique_subquery包含 IN 的查询,决定将 IN 子查询转换为 EXISTS 子查询,而且转换后子查询可以使用主键或不包含 NULL 的唯一二级索引进行等值匹配查询,该子查询的方式
index_subquery与 unique_subquery 类似,访问子查询中的表时使用普通索引
index可以使用索引覆盖,但需要扫描全部的索引数据

(5)使用 index 访问方式时,possible_keys 列是为 NULL,而 key 列会展示实际用到的索引。

(6)key_len;实际使用的索引长度(作用:从查询计划可以看出形成扫描区间的边界条件是什么,尤其是在联合索引中,通过 key_len 就知道使用前几个列

该列实际数据最多占用的字节数,例如 使用 utf8 字符集,类型为 VARCHAR(100),key_len = 300;
如果该列可以存储 NULL 值, key_len + 1;
对变长类型来说,会有存储变长长度列表,因此 key_len + 2;
(注意,这里是 server 层中的功能,并不针对某个具体的存储引擎,全都按照最大的标准来)

(7)ref;与索引列等值匹配的对象,可以是常数 const、某个列、函数 func 。

(8)rows;使用 ALL 或 range 单表访问方法时,预估的行数。

(9)filtered;百分值,预估有占比多少的记录满足条件。主要用于 Join 操作中驱动表的预估。

(10)Extra;

类型描述
Impossible WHEREWHERE 子句永远为 False
No matching min/max row查询列表中有 MIN 或 MAX 函数,但没有记录符合 WHERE 子句中的搜索条件
Using index使用覆盖索引执行查询
Using index condition搜索条件中出现了索引列,但不能充当边界条件(例如,‘%a’),即索引条件下推 (只适用于二级索引,只有形成扫描区间的条件时也会有该提示)
Using where某个条件需要在 server 层判断
Using join buffer (Block Nested Loop)Join 查询时,被驱动表不能使用索引加快访问速度,会分配连接缓冲区
Using intersect()、union()、sort_union()使用索引合并
Using filesort使用文件排序
Using temporaryDISTINCT、GROUP BY、UNION等子句不能有效使用索引,需要使用临时表;注意:MySQL 在包含 GROUP BY 子句的查询中默认添加 ORDER BY 子句 ,若不想排序,需要显示写出 ORDER BY NULL(最好使用索引替代临时表)
Start temporary, End temporary将 IN 子查询转换为半连接时,执行策略为 Duplicate Weedout,驱动表提示 Start temporary,被驱动表提示 End temporary
LooseScan将 IN 子查询转换为半连接时,执行策略为 LooseScan
FirstMatch(tbl_name)将 IN 子查询转换为半连接时,执行策略为 FirstMatch(tbl_name)

3. optimizer trace

(1)作用:可以让用户方便地查看优化器生成执行计划的整个过程

(2)这个功能的开启和关闭由系统变量 optimizer_trace 决定,默认为关闭。

开启:SET optimizer_trace="enabled=on"

(3)当执行完查询语句或 EXPLAN 查看该语句执行计划后,到 information_schema 下的 OPTIMIZER_TRACE 表中查看完整的执行计划的生成过程。

  • 4
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值