0
点赞
收藏
分享

微信扫一扫

mysql8.0 之 sql 优化《三A》 之 优化子查询,派生表,查看引用和公用表表达式 总览 总结


介绍

mysql 优化器有不同的策略用于对应的子查询:

  • 对于in​​IN​​​ (or​​=ANY​​)  子查询又好看可以使用:
  • 办连接
  • 实体化exists 策略

 

对于 not int(or<>all) 子查询,优化器提供如下选项:

  • 实体化
  • exists策略

 

对应派生表,可以使用如下(也适用视图引用和公用表达式)

  • 将派生表合并到外不查询快中
  • 将派生表实现内部临时表

 

注意:

  • 使用子查询修改单个表的限制 update and delete 语句,是不适用板链机or 实现子查询优化。
  • 还有一种解决方案尝试将他们重新为使用链接而不是子查询的多表方式 执行update or delete sql

 

使用半连接转换优化子查询,派生表,

  • 对于两个表之间的内部联接,连接从一个表返回一行,与另一个表中的匹配一样多次。但对于一些问题,唯一重要的信息是匹配,而不是匹配的数量。

SELECT class.class_num, class.class_name
FROM class INNER JOIN roster
WHERE class.class_num = roster.class_num;

使用子查询可以获得相同的无重复结果:

SELECT class_num, class_name
FROM class
WHERE class_num IN (SELECT class_num FROM roster);

外部查询规范中允许外部联接和内部联接语法,表引用可以是基表,派生表,视图引用或公用表表达式。

在MySQL中,子查询必须满足这些条件才能作为半连接处理:

  • 它必须是出现在or 子句顶层的​​IN​​(或 ​​=ANY​​)子查询,可能作为表达式中的术语 。例如: ​​WHERE​​​​ON​​​​AND​​

SELECT ...
FROM ot1, ...
WHERE (oe1, ...) IN (SELECT ie1, ... FROM it1, ... WHERE ...);

这里, 和 表示在该查询的外侧和内侧的部分表,和 与 表示参照列中的外和内表中的表达式。 ​​ot_i​​​it_i​​​oe_i​​​ie_i

  • 它必须是select没有union结构的单一。
  • 它不能包含​​GROUP BY​​​or​​HAVING​​子句。
  • 它不能隐式分组(它必须不包含聚合函数)。
  • 它一定不能​​ORDER BY​​​用​​LIMIT​​。
  • 该语句不得​​STRAIGHT_JOIN​​在外部查询中使用 连接类型。
  • 该​​STRAIGHT_JOIN​​修改必须不存在。
  • 外表和内表的数量必须小于连接中允许的最大表数。

表相关和不向关都可以用,distinct 是运行的,limit除非order by 也适用 不可以

如果查询符合上边条件mysql 会自动转成半链接,并根据策略,进行成本的选择。

因为处理中的更改会导致转换发生更改,从而导致执行策略不同:

CREATE VIEW v AS
SELECT *
FROM t1
WHERE a IN (SELECT b
FROM t2);

SELECT STRAIGHT_JOIN *
FROM t3 JOIN v ON t3.x = v.a;

优化器首先查看视图并将 ​​IN​​​子查询转换为半连接,然后检查是否可以将视图合并到外部查询中。由于​​STRAIGHT_JOIN​​外部查询中的修饰符会阻止半连接,因此优化程序会拒绝合并,从而导致使用实现表进行派生表评估

 

 

使用实现优化子查询

  • 优化程序使用实现来实现更有效的子查询处理。实现通过生成子查询结果作为临时表(通常在内存中)来加速查询执行。MySQL第一次需要子查询结果时,它会将结果实现为临时表。在任何后续需要结果的时候,MySQL再次引用临时表。优化器可以使用哈希索引对表进行索引,以使查找快速且廉价。索引包含唯一值以消除重复项并使表更小。
  • 子查询实现在可能的情况下使用内存中的临时表,如果表变得太大,则会回退到磁盘上的存储。
  • 如果未使用实现,则优化程序有时会将非相关子查询重写为相关子查询。例如,以下​​IN​​​子查询是不相关的(​where_condition​​ 仅涉及列​​t2​​​和不包括列​​t1​​):

SELECT * FROM t1
WHERE t1.a IN (SELECT t2.b FROM t2 WHERE where_condition);

优化器可能会将其重写为 ​​EXISTS​​相关子查询:

SELECT * FROM t1
WHERE EXISTS (SELECT t2.b FROM t2 WHERE where_condition AND t1.a=t2.b);

使用临时表的子查询实现避免了这种重写,并且可以仅执行一次子查询,而不是每次执行外部查询一次。

 

对于要在MySQL中使用的子查询实现, 必须启用optimizer_switch系统变量​​materialization​​​标志。随着​​materialization​​​启用的标志,物化适用于子查询任何地方出现谓词(在选择列表中,​​WHERE​​​, ​​ON​​​,​​GROUP BY​​​, ​​HAVING​​​,或​​ORDER BY​​),对于属于任何这些用例谓词:

  • 当没有外部表达式​oe_i​​或内部表达式 ​ie_i​​可为空时,谓词具有此形式 。 ​N​是1或更大。

(oe_1, oe_2, ..., oe_N) [NOT] IN (SELECT ie_1, i_2, ..., ie_N ...)

  • 当存在单个外部表达式​oe​​和内部表达式时,谓词具有此形式​ie​。表达式可以为空。

oe [NOT] IN (SELECT ie ...)

  • 谓词是​​IN​​​或者 ()​​NOT IN​​​的结果具有与结果相同的含义。​​UNKNOWN​​​​NULL​​​​FALSE​

以下示例说明了等价​​UNKNOWN​​​和 ​​FALSE​​​谓词评估的要求如何影响是否可以使用子查询实现。假设 ​where_condition​​只涉及列​​t2​​​而不是​​t1​​ 子查询是不相关的。

此查询取决于具体化:

SELECT * FROM t1
WHERE t1.a IN (SELECT t2.b FROM t2 WHERE where_condition);

在这里,没有关系是否​​IN​​​ 断言返回​​UNKNOWN​​​或 ​​FALSE​​​。无论哪种方式,​​t1​​查询结果中都不包含行 。

不使用子查询实现的示例是以下查询,其中​​t2.b​​是可空列:

SELECT * FROM t1
WHERE (t1.a,t1.b) NOT IN (SELECT t2.a,t2.b FROM t2
WHERE where_condition);

以下限制适用于子查询实现的使用:

  • 内表达式和外表达式类型必须匹配,例如俩个都是整数或都是十进制,则优化器可以实现,但俩个如果不同,则不实现
  • 内在表达式不能是一个BLOB(Blob是一个二进制的对象,它是一个可以存储大量数据的容器(如图片,音乐等等))

使用 explainwith chax  可以只是优化程序使用子查询实现

  • 与不使用实现的查询执行相比,​​select_type​​​可能会更改​​DEPENDENT SUBQUERY​​​为​​SUBQUERY​​。这表明,对于每个外行将执行一次的子查询,实现使子查询只执行一次。

使用EXISTS策略优化子查询

某些优化适用于使用​​IN​​​(或​​=ANY​​​)运算符测试子查询结果的比较。本节讨论这些优化,特别是关于​​NULL​​价值所带来的挑战。讨论的最后部分建议您如何帮助优化器。

考虑以下子查询比较:

outer_expr IN (SELECT inner_expr FROM ... WHERE subquery_where)

MySQL的评估查询“ 从外到内。“ 也就是说,它首先获取外部表达式的值 ​outer_expr​,然后运行子查询并捕获它生成的行。

一个非常有用的优化是“ 通知 ”子查询,只有感兴趣的行是内部表达式​inner_expr​​相等的行​outer_expr​​。这是通过将适当的相等性下推到子查询的 ​​WHERE​​子句中来实现的,以使其更具限制性。转换后的比较如下所示:

EXISTS (SELECT 1 FROM ... WHERE subquery_where AND outer_expr=inner_expr)

转换后,MySQL可以使用下推的相等性来限制它必须检查以评估子查询的行数。

更一般地,​N​​ 值与返回​N​​-value行的子查询 的比较受到相同的转换。如果​oe_i​​并 ​ie_i​表示相应的外部和内部表达式值,则此子查询比较:

(oe_1, ..., oe_N) IN
(SELECT ie_1, ..., ie_N FROM ... WHERE subquery_where)

变为:

EXISTS (SELECT 1 FROM ... WHERE subquery_where
AND oe_1 = ie_1
AND ...
AND oe_N = ie_N)

 

如果​outer_expr​​是 ​​NULL​​,要评估以下表达式,有必要执行 select 以确定它是否产生任何行:

NULL IN (SELECT inner_expr FROM ... WHERE subquery_where)

 

使用合并或实现优化派生表

优化器可以使用两种策略(也适用于视图引用和公用表表达式)处理派生表引用:

  • 将派生表合并到外部查询块中
  • 将派生表实现为内部临时表

例1:

SELECT * FROM (SELECT * FROM t1) AS derived_t1;

通过合并派生表 ​​derived_t1​​,该查询的执行类似于:

SELECT * FROM t1;

例2:

SELECT *
FROM t1 JOIN (SELECT t2.f1 FROM t2) AS derived_t2 ON t1.f2=derived_t2.f1
WHERE t1.f1 > 0;

通过合并派生表 ​​derived_t2​​,该查询的执行类似于:

SELECT t1.*, t2.f1
FROM t1 JOIN t2 ON t1.f2=t2.f1
WHERE t1.f1 > 0;

 

具有实现,​​derived_t1​​​并 ​​derived_t2​​在每个查询中被视为单独的表。

优化器以相同的方式处理派生表,视图引用和公用表表达式:它尽可能避免不必要的实现,这样可以将条件从外部查询推送到派生表,并生成更高效的执行计划。

如果合并将导致外部查询块引用超过61个基表,则优化程序将选择实现。

​ORDER BY​​如果这些条件都为真 ,则优化器将派生表或视图引用中的子句传播到外部查询块:

  • 外部查询未分组或聚合。
  • 外部查询不指定​​DISTINCT​​​,​​HAVING​​​或​​ORDER BY​​。
  • 外部查询将此派生表或视图引用作为​​FROM​​子句中的唯一源。

否则,优化器会忽略该​​ORDER BY​​子句。

以下方法可用于影响优化程序是否尝试将派生表,视图引用和公用表表达式合并到外部查询块中:

  • 该merge和 no_merge优化器提示可以使用。假设没有其他规则阻止合并,它们适用。
  • 同样,您可以使用系统变量的​​derived_merge​​​标志 optimizer_switch默认情况下,启用该标志以允许合并。禁用该标志可防止合并并避免 er_update_table_used 错误。
    该​​​derived_merge​​​标志也适用于不包含​​ALGORITHM​​​子句的视图。因此,如果 er_update_table_used使用等效于子查询的表达式的视图引用发生错误,则添加​​ALGORITHM=TEMPTABLE​​​到视图定义会阻止合并并优先于该​​derived_merge​​值。
  • 可以通过在子查询中使用任何阻止合并的构造来禁用合并,尽管这些构造对实现的影响并不明确。对于派生表,公用表表达式和视图引用,阻止合并的构造是相同的:
  • 聚集函数或窗函数(sum(),min(),max(),count(),等等)
  • ​DISTINCT​
  • ​GROUP BY​
  • ​HAVING​
  • ​LIMIT​
  • ​union​​ 要么 union all
  • 选择列表中的子查询
  • 分配给用户变量
  • 仅引用文字值(在这种情况下,没有基础表)

如果优化器选择实现策略而不是合并派生表,它将按如下方式处理查询:

  • 优化器推迟派生表实现,直到在查询执行期间需要其内容。这提高了性能,因为延迟实现可能导致根本不必执行此操作。考虑将派生表的结果连接到另一个表的查询:如果优化器首先处理该另一个表并发现它不返回任何行,则不需要进一步执行连接,并且优化器可以完全跳过实现派生表的实现。
  • 在查询执行期间,优化器可以向派生表添加索引以加速从中检索行。

​explain​​ 对于select包含派生表的查询, 请考虑以下语句:

EXPLAIN SELECT * FROM (SELECT * FROM t1) AS derived_t1;

优化程序通过延迟派生表来避免实现派生表,直到select执行期间需要结果为止 。在这种情况下,不执行查询(因为它出现在 explain语句中),因此永远不需要结果。

即使对于执行的查询,派生表实现的延迟也可能使优化器完全避免实现。发生这种情况时,执行实现所需的时间会更快地执行查询。请考虑以下查询,该查询将派生表的结果连接到另一个表:

SELECT *
FROM t1 JOIN (SELECT t2.f1 FROM t2) AS derived_t2
ON t1.f2=derived_t2.f1
WHERE t1.f1 > 0;

如果优化进程​​t1​​​首先并且​​WHERE​​子句产生空结果,则连接必须为空并且不需要实现派生表。

对于派生表需要实现的情况,优化器可以向实现表添加索引以加速对其的访问。如果这样的索引允许 ​​ref​​访问表,则可以大大减少查询执行期间读取的数据量。请考虑以下查询:

SELECT *
FROM t1 JOIN (SELECT DISTINCT f1 FROM t2) AS derived_t2
ON t1.f1=derived_t2.f1;

优化器在列上构造索引 ​​f1​​​,​​derived_t2​​如果这样做将允许使用ref最低成本执行计划的 访问。添加索引后,优化程序可以将具体化派生表视为具有索引的常规表,并且与生成的索引类似。与没有索引的查询执行成本相比,索引创建的开销可以忽略不计。如果 ref访问会导致比其他访问方法更高的成本,则优化程序不会创建任何索引并且不会丢失任何内容。

 

 

 

上篇优化《三》 之 优化数据更新 insert delete update

 

 

文章持续更新,转发表明出处,方便更新!

 

 

 

举报

相关推荐

0 条评论