-a<-8 或 ABS(a) > 5
WHERE 搜索条件中指定 “被驱动表的列不能为 NULL”时(空值拒绝,例如指定被驱动表的某列等于一个常数),外连接和内连接可以相互转换。
(1)在 SELECT 子句中;子查询必须为标量子查询;
(2)在 FROM 子句中;
这里是把子查询的查询结果当作一个表,称为派生表
(3)在 WHERE 或 ON 子句中;
(1)使用 =、>、<、>=、<=、<>、!=、<=> 做为布尔表达式的操作符
操作数 comparison_operator (子查询)
这里的子查询只能说标量子查询(返回单个值)或行子查询(返回单行记录)。
(2)[NOT] IN / ANY / SOME / ALL;子查询中不允许有 LIMIT 语句。
子查询为列子查询(单列)或表子查询(多行多列)。
操作数 [NOT] IN (子查询);
操作数 comparison_operator ANY/SOME/ALL(子查询);
[NOT] EXISTS (子查询);判断子查询的结果集中是否有记录
不允许在一条语句中增删改某个表的记录时,同时还对该表进行子查询。
(1)标量子查询、行子查询(原始方式)
对不相关的标量子查询或行子查询,分别单独执行子查询和外层查询。
对相关的,先从外层查询获取一条记录,执行子查询,再判断 WHERE 条件是否满足。
(1)不相关的子查询
物化表;将子查询结果保存到临时表中。(写入临时表的记录会被去重,通过对结果集所有列建立联合主键或联合唯一索引实现)
存放在内存 MEMORY 引擎中的物化表会有哈希索引,存放在磁盘上的有 B+ 树索引(根据子查询结果集的大小判断是否大于tmp_table_size
或max_heap_table_size
,从而决定放在内存还是磁盘)。
通过索引来判断某个操作数是否存在子查询结果集中时,速度就比较快了。
物化表转连接;
转成内连接。
(2)不进行物化的方案,直接把子查询转换为连接。
将子查询转换为半连接
半连接是在 MySQL 内部采用的一种执行子查询的方式,多条记录匹配最终只保存一条记录。
对 INNER JOIN 来讲,s1.key1 IN (s2.key2_v1, …) 与 s1 INNER JOIN s2 ON s1.key1 = s2.key2 相比,在内连接中对于 s1 表中的某个key1 记录,在 s2 表中可能有多条 key2 记录与之匹配,这会导致该记录被多次加入结果集。与 IN 的语义并不完全一致。
半连接的执行策略
这里的原理是:想办法消除子查询或结果表中的重复记录。
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)不使用半连接的情况
对于不相关的子查询,会尝试将它们物化之后再参与查询。
把 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 个方法哪个成本最低。
如果 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) …) |
如果是不相关子查询,可以先执行子查询,使用 TRUE 或 FALSE 替换即可。
对相关子查询,只能按照原始方式执行。最好可以在子查询使用索引。
把派生表物化;会使用延迟物化的策略,即推迟到真正使用时才进行物化。
把派生表与外层查询合并。
(1)无法与外层查询合并的情况。
派生表中包含聚合函数、DISTINCT、GROUP BY、HAVING、LIMIT、UNION、子查询包含其他子查询等时。
(2)总结:MySQL在执行时,会优先尝试把派生表与外层循环合并,如果不行,再将派生表物化。
列名 | 描述 |
---|---|
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 WHERE | WHERE 子句永远为 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 temporary | DISTINCT、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) |
(1)作用:可以让用户方便地查看优化器生成执行计划的整个过程
(2)这个功能的开启和关闭由系统变量 optimizer_trace
决定,默认为关闭。
开启:SET optimizer_trace="enabled=on"
(3)当执行完查询语句或 EXPLAN 查看该语句执行计划后,到 information_schema 下的 OPTIMIZER_TRACE 表中查看完整的执行计划的生成过程。