优化器可以使用两种策略来处理派生表引用(也适用于视图引用和公用表表达式)
将派生表合并到外部查询块中
将派生表物化为内部临时表
示例 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
在各自的查询中被视为独立的表。
优化器以相同的方式处理派生表、视图引用和公用表表达式:只要有可能,它就会避免不必要的物化,这使得能够将外部查询中的条件下推到派生表,并产生更高效的执行计划。(例如,请参见第 10.2.2.2 节,“使用物化优化子查询”。)
如果合并会导致外部查询块引用超过 61 个基本表,则优化器会选择物化。
如果以下所有条件都为真,则优化器会将派生表或视图引用中的 ORDER BY
子句传播到外部查询块
外部查询没有分组或聚合。
外部查询没有指定
DISTINCT
、HAVING
或ORDER BY
。外部查询的
FROM
子句中仅包含此派生表或视图引用作为源。
否则,优化器会忽略 ORDER BY
子句。
可以使用以下方法来影响优化器是否尝试将派生表、视图引用和公用表表达式合并到外部查询块中
可以使用
MERGE
和NO_MERGE
优化器提示。它们在假设没有其他规则阻止合并的情况下适用。请参见第 10.9.3 节,“优化器提示”。类似地,您可以使用
derived_merge
标志,该标志是optimizer_switch
系统变量的一部分。请参见第 10.9.2 节,“可切换优化”。默认情况下,该标志已启用以允许合并。禁用该标志会阻止合并,并避免ER_UPDATE_TABLE_USED
错误。derived_merge
标志也适用于不包含ALGORITHM
子句的视图。因此,如果对于使用与子查询等效的表达式的视图引用发生ER_UPDATE_TABLE_USED
错误,则在视图定义中添加ALGORITHM=TEMPTABLE
可以阻止合并,并且优先于derived_merge
值。可以通过在子查询中使用任何阻止合并的构造来禁用合并,尽管这些构造在对物化的影响上并不那么明确。用于阻止合并的构造对于派生表、公用表表达式和视图引用是相同的
如果优化器选择物化策略而不是合并派生表,它会按如下方式处理查询
优化器会推迟派生表物化,直到查询执行期间需要其内容。这可以提高性能,因为延迟物化可能导致完全不需要进行物化。考虑一个将派生表的结果与另一个表联接的查询:如果优化器首先处理另一个表并发现它没有返回任何行,则联接无需进一步执行,并且优化器可以完全跳过派生表的物化。
在查询执行期间,优化器可能会在派生表上添加索引以加快从派生表中检索行的速度。
考虑以下 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;
如果这样做能够实现 ref
访问用于最低成本执行计划,则优化器将从 derived_t2
中构建一个在列 f1
上的索引。添加索引后,优化器可以像对待具有索引的常规表一样对待物化的派生表,并且它可以从生成的索引中同样受益。与没有索引的查询执行成本相比,创建索引的开销可以忽略不计。如果 ref
访问会导致比其他一些访问方法更高的成本,则优化器不会创建索引,也不会损失任何东西。
对于优化器跟踪输出,合并的派生表或视图引用不会显示为节点。只有其底层表出现在顶级查询的计划中。
派生表物化的真实情况也适用于公用表表达式(CTE)。此外,以下考虑事项专门针对 CTE。
如果查询对 CTE 进行物化,则会为该查询物化一次,即使该查询多次引用它。
递归 CTE 始终会被物化。
如果对 CTE 进行物化,优化器会自动添加相关的索引,如果它估计索引可以加快顶级语句访问 CTE 的速度。这与派生表的自动索引类似,不同之处在于,如果多次引用 CTE,优化器可能会创建多个索引,以最合适的方式加快每个引用的访问速度。
可以将 MERGE
和 NO_MERGE
优化器提示应用于 CTE。顶级语句中的每个 CTE 引用都可以有自己的提示,允许选择性地合并或物化 CTE 引用。以下语句使用提示来指示应该合并 cte1
并且应该物化 cte2
WITH
cte1 AS (SELECT a, b FROM table1),
cte2 AS (SELECT c, d FROM table2)
SELECT /*+ MERGE(cte1) NO_MERGE(cte2) */ cte1.b, cte2.d
FROM cte1 JOIN cte2
WHERE cte1.a = cte2.c;
用于 CREATE VIEW
的 ALGORITHM
子句不会影响视图定义中 SELECT
语句之前的任何 WITH
子句的物化。考虑以下语句
CREATE ALGORITHM={TEMPTABLE|MERGE} VIEW v1 AS WITH ... SELECT ...
仅 SELECT
的物化会受到 ALGORITHM
值的影响,而不是 WITH
子句。
如前所述,如果对 CTE 进行物化,即使它被多次引用,也会物化一次。为了表明一次性物化,优化器跟踪输出包含一个 creating_tmp_table
的出现以及一个或多个 reusing_tmp_table
的出现。
CTE 与派生表类似,对于派生表,materialized_from_subquery
节点位于引用之后。对于多次引用的 CTE,这是正确的,因此没有 materialized_from_subquery
节点的重复(这会给人一种子查询被多次执行的印象,并产生不必要的冗长输出)。只有一个对 CTE 的引用具有完整的 materialized_from_subquery
节点,其中包含其子查询计划的描述。其他引用具有简化的 materialized_from_subquery
节点。相同的想法适用于 EXPLAIN
输出的 TRADITIONAL
格式:其他引用的子查询不会显示。