优化器可以使用两种策略处理派生表引用(这也适用于视图引用和公共表表达式):
-
将派生表合并到外部查询块中
-
将派生表物化到内部临时表中
示例 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;
优化器将在 derived_t2
上构建一个索引,以便在最低成本的执行计划中使用 ref
访问。如果添加索引,优化器可以将物化的派生表视为一个普通的索引表,并从生成的索引中获益。索引创建的开销与查询执行的成本相比可以忽略。如果 ref
访问将导致更高的成本,那么优化器将不创建索引,也不会损失什么。
对于优化器跟踪输出,合并的派生表或视图引用不会显示为节点。只有其基础表在顶级查询计划中出现。
派生表物化的真理也适用于公共表表达式(CTEs)。此外,以下考虑也适用于 CTEs。
如果 CTE 被查询物化,它将被物化一次,即使查询引用它多次。
递归 CTE 总是被物化。
如果 CTE 被物化,优化器将自动添加相关索引,以便加速顶级语句对 CTE 的访问。这类似于派生表的自动索引,除了如果 CTE 被多次引用,优化器可能创建多个索引,以便加速每个引用的访问方式。
可以将 MERGE
和 NO_MERGE
优化器提示应用于 CTEs。每个 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
子句不影响任何 WITH
子句在视图定义中的物化。考虑以下语句:
CREATE ALGORITHM={TEMPTABLE|MERGE} VIEW v1 AS WITH ... SELECT ...
ALGORITHM
值仅影响视图定义中的 SELECT
的物化,而不是 WITH
子句。
如前所述,如果 CTE 被物化,它将被物化一次,即使被多次引用。为了表明一次物化,优化器跟踪输出将包含一个 creating_tmp_table
及一个或多个 reusing_tmp_table
。
CTEs 与派生表相似,对于派生表,materialized_from_subquery
节点将跟随引用。这也适用于 CTE,即使它被多次引用,也不会出现 materialized_from_subquery
节点的重复(这将给人以子查询被多次执行的印象,并产生不必要的冗长输出)。只有一个 CTE 引用具有完整的 materialized_from_subquery
节点,描述其子查询计划。其他引用具有简化的 materialized_from_subquery
节点。同样的想法也适用于 EXPLAIN
输出的传统格式:其他引用的子查询不显示。