查询规划——预处理
- 预处理
- 提升子链接/子查询
- 预处理表达式
- 预处理HAVING子句
声明:本文的部分内容参考了他人的文章。在编写过程中,我们尊重他人的知识产权和学术成果,力求遵循合理使用原则,并在适用的情况下注明引用来源。
本文主要参考了《PostgresSQL数据库内核分析》一书
预处理
前面说过,在实际进行计划生成之前将对查询树做一些预处理,预处理的主要工作是提升子链接和子查询以及预处理表达式和HAVING子句等。预处理部分主要是对查询树Query中的范围表rtable和连接树jointree等进行处理。
查询规划的预处理阶段是查询优化的第一步,其目的是对用户提交的查询进行初步处理和解析,准备生成查询树。预处理阶段包括以下主要步骤:
- 词法和语法分析:将用户输入的SQL查询语句转换成抽象语法树(AST)的形式,将查询语句拆分成独立的语法单元,检查语法是否合法。
- 语义分析:对查询进行语义检查,验证表、列等数据库对象的存在性和合法性,检查查询语句是否符合数据库规则。
- 名称解析:解析查询语句中的标识符(如表名、列名、别名等),将它们与数据库中的实际对象进行绑定,以便后续阶段能够正确地处理这些对象。
- 权限检查:检查用户是否有执行查询所需的权限,如果没有权限,则阻止查询执行。
- 优化器挑选:在预处理阶段,优化器可能根据查询特性进行一些简单的优化,如选择最佳的连接顺序、使用合适的索引等。
预处理阶段完成后,生成了一个包含查询信息的解析树(Parse Tree)。这个解析树会被进一步处理,进行查询重写、路径生成和生成计划等优化过程,最终生成可执行的查询计划并执行查询。
提升子链接/子查询
子查询(Subquery)和子链接(Sublink)是两个不同的概念,它们在SQL查询中有不同的用途和作用。
子查询(Subquery):
子查询是指一个完整的查询语句嵌套在另一个查询语句中的查询。它通常作为主查询的条件或数据源来限制或过滤主查询的结果集。子查询可以出现在SELECT、FROM、WHERE、HAVING等子句中,并返回一个单一值、一组值或一个结果集供主查询使用。
例如,在以下SQL语句中,子查询用作WHERE子句中的条件:
SELECT * FROM employees WHERE department_id = (SELECT department_id FROM departments WHERE department_name = 'HR');
子链接(Sublink)::
子链接是优化器在查询规划阶段对子查询的一种处理方式。它将子查询转换成一个虚拟的关系表(虚拟表),并将其视为一个普通的表参与查询规划的过程。子链接有时也称为“提升子查询”或“优化子查询”。
子链接的作用是将子查询的结果缓存为一个虚拟表,以避免在每次查询时都重复执行子查询。这样做可以提高查询性能,特别是在子查询较为复杂或返回结果集较大时。
在查询规划的预处理阶段中,“提升子链接”,也称为"提升子查询",是一种优化技术,用于将子查询转换为更高效的连接操作,以提高整体查询性能。
通常,子查询是嵌套在主查询中的查询语句,它会在主查询的执行过程中被执行多次。这可能导致性能问题,特别是当子查询返回大量数据时。
"提升子链接"的目的是将子查询转换为连接操作或使用关联子查询等方式,以减少子查询的执行次数,从而减少不必要的开销。这样可以有效地优化查询计划,提高查询性能。
这里分别对 查询块,嵌套查询,嵌套子查询 这几个名词进行解释。
- 查询块(Query Block):
查询块是指一个独立的查询语句或子查询,在SQL语句中通常由一个SELECT、INSERT、UPDATE、DELETE等关键字引导,并包含其后的所有相关子句。查询块可以是整个SQL语句,也可以是一个复杂查询中的一部分。查询块的作用是定义一个查询的逻辑结构和语义,它可以由优化器进行优化和执行。
例如,在以下SQL语句中,有两个查询块:主查询块和子查询块。
SELECT * FROM employees WHERE department_id = (SELECT department_id FROM departments WHERE department_name = 'HR');
- 嵌套查询(Nested Query):
嵌套查询是指一个查询语句中包含了另一个完整的查询语句,也称为子查询。嵌套查询的内部查询通常被称为子查询,外部查询称为主查询。嵌套查询允许在查询中使用查询的结果作为另一个查询的条件或数据源,从而实现更复杂的查询需求。
例如,在以下SQL语句中,就有一个嵌套查询:
SELECT * FROM employees WHERE department_id = (SELECT department_id FROM departments WHERE department_name = 'HR');
- 嵌套子查询(Nested Subquery):
嵌套子查询是指在一个查询语句的条件中使用了一个完整的子查询。嵌套子查询与嵌套查询的概念相似,但重点在于嵌套子查询作为主查询的条件之一,用于限制主查询的结果集。
例如,在以下SQL语句中,嵌套子查询用于限制主查询的结果集:
SELECT * FROM employees WHERE department_id IN (SELECT department_id FROM departments WHERE location = 'New York');
按相关性,嵌套查询可分为相关子查询和非相关子查询。相关子查询是指该子查询的执行依赖于外层父查询的某些属性值。它需要接受父查询的参数,因此会设置相关标记,当参数改变的时候,需要重置参数后重新执行一遍子查询得到新的结果。非相关子查询中子查询完全可以独立。
- 相关子查询(Correlated Subquery):
相关子查询是指子查询中的条件依赖于外部查询的结果,在子查询中使用了外部查询的列值。换句话说,子查询的执行依赖于外部查询的每一行。相关子查询在执行时会为外部查询的每一行执行一次,并根据外部查询的值来计算子查询的结果。
例如,在以下SQL语句中,子查询中的条件依赖于外部查询的employee_id:
SELECT * FROM employees e
WHERE salary > (SELECT AVG(salary) FROM employees WHERE department_id = e.department_id);
- 非相关子查询(Non-Correlated Subquery):
非相关子查询是指子查询中的条件与外部查询无关,子查询的执行不依赖于外部查询的结果。非相关子查询在执行时只执行一次,不受外部查询的影响,可以独立地计算子查询的结果。
例如,在以下SQL语句中,子查询中的条件与外部查询无关,只需要执行一次来计算最大的salary:
SELECT * FROM employees
WHERE salary > (SELECT MAX(salary) FROM employees);
总结:相关子查询是子查询中的条件依赖于外部查询的结果,会为外部查询的每一行执行一次。非相关子查询是子查询中的条件与外部查询无关,只执行一次。
预处理表达式
表达式的预处理工作由函数preprocess_expression完成,处理的对象有:目标属性、HAVING子句、OFFSET 和 LIMIT子句、连接树。preprocess_expression 采用递归扫描的方式对PlannerImfo结构中的表达式进行处理。
preprocess_expression函数源码如下:(路径:src/backend/optimizer/plan/planner.c
)
static Node *
preprocess_expression(PlannerInfo *root, Node *expr, int kind)
{
/*
* Fall out quickly if expression is empty. This occurs often enough to
* be worth checking. Note that null->null is the correct conversion for
* implicit-AND result format, too.
*/
if (expr == NULL)
return NULL;
/*
* If the query has any join RTEs, replace join alias variables with
* base-relation variables. We must do this first, since any expressions
* we may extract from the joinaliasvars lists have not been preprocessed.
* For example, if we did this after sublink processing, sublinks expanded
* out from join aliases would not get processed. But we can skip this in
* non-lateral RTE functions, VALUES lists, and TABLESAMPLE clauses, since
* they can't contain any Vars of the current query level.
*/
if (root->hasJoinRTEs &&
!(kind == EXPRKIND_RTFUNC ||
kind == EXPRKIND_VALUES ||
kind == EXPRKIND_TABLESAMPLE ||
kind == EXPRKIND_TABLEFUNC))
expr = flatten_join_alias_vars(root, expr);
/*
* Simplify constant expressions.
*
* Note: an essential effect of this is to convert named-argument function
* calls to positional notation and insert the current actual values of
* any default arguments for functions. To ensure that happens, we *must*
* process all expressions here. Previous PG versions sometimes skipped
* const-simplification if it didn't seem worth the trouble, but we can't
* do that anymore.
*
* Note: this also flattens nested AND and OR expressions into N-argument
* form. All processing of a qual expression after this point must be
* careful to maintain AND/OR flatness --- that is, do not generate a tree
* with AND directly under AND, nor OR directly under OR.
*/
expr = eval_const_expressions(root, expr);
/*
* If it's a qual or havingQual, canonicalize it.
*/
if (kind == EXPRKIND_QUAL)
{
expr = (Node *) canonicalize_qual((Expr *) expr);
#ifdef OPTIMIZER_DEBUG
printf("After canonicalize_qual()\n");
pprint(expr);
#endif
}
/* Expand SubLinks to SubPlans */
if (root->parse->hasSubLinks)
expr = SS_process_sublinks(root, expr, (kind == EXPRKIND_QUAL));
/*
* XXX do not insert anything here unless you have grokked the comments in
* SS_replace_correlation_vars ...
*/
/* Replace uplevel vars with Param nodes (this IS possible in VALUES) */
if (root->query_level > 1)
expr = SS_replace_correlation_vars(root, expr);
/*
* If it's a qual or havingQual, convert it to implicit-AND format. (We
* don't want to do this before eval_const_expressions, since the latter
* would be unable to simplify a top-level AND correctly. Also,
* SS_process_sublinks expects explicit-AND format.)
*/
if (kind == EXPRKIND_QUAL)
expr = (Node *) make_ands_implicit((Expr *) expr);
return expr;
}
preprocess_expression中涉及的函数功能介绍:
函数名 | 作用 |
---|---|
flatten_join_alias_vars | 用基本关系变量取代连接别名变量 |
eval_const_expressions | 进行常量表达式的简化 |
canonicalize_qual | 对表达式进行规范化 |
预处理HAVING子句
在查询规划中,预处理HAVING子句是指对查询的HAVING条件进行处理和转换,以便为后续优化和执行过程做准备。HAVING子句用于在GROUP BY聚合后对结果集进行过滤,类似于WHERE子句,但是作用于分组后的结果。