作者:chirpyli
来源:恒生LIGHT云社区
子查询的逻辑优化内容很多,我们先以 select * from t1 where id in (select a from t2);
这条SQL语句为例分析PostgreSQL对子连接的逻辑优化过程。
在往下讲之前,先区别一下子连接与子查询,因为代码中分别会对子连接和子查询进行上拉操作。怎么区别呢?通常,以范围表的方式存在的称为子查询,以表达式的方式存在的称为子连接。实际上也可以通过子句所处的位置来区分子连接和子查询,出现在FROM关键字之后的子句时子查询语句,出现在WHERE等约束条件中的是子连接。
我们先通过执行计划进行观察分析,数据库通过 Hash Semi Join
对子连接进行了逻辑优化。
--查看执行计划
postgres=# explain select * from t1 where id in (select a from t2);
QUERY PLAN
---------------------------------------------------------------
Hash Semi Join (cost=1.09..2.21 rows=4 width=8)
Hash Cond: (t1.id = t2.a)
-> Seq Scan on t1 (cost=0.00..1.06 rows=6 width=8)
-> Hash (cost=1.04..1.04 rows=4 width=4)
-> Seq Scan on t2 (cost=0.00..1.04 rows=4 width=4)
解析层分析
我们先分析子连接在解析层的表示(以这条语句为例,其他子连接形式类似):
/* A SubLink represents a subselect appearing in an expression, and in some
* cases also the combining operator(s) just above it. */
typedef struct SubLink
{
Expr xpr;
SubLinkType subLinkType; /* see above */
int subLinkId; /* ID (1..n); 0 if not MULTIEXPR */
Node *testexpr; /* outer-query test for ALL/ANY/ROWCOMPARE */
List *operName; /* originally specified operator name */
Node *subselect; /* subselect as Query* or raw parsetree */
int location; /* token location, or -1 if unknown */
} SubLink;
语法定义如下:
// 匹配 where id in (select a from t2);
where_clause:
WHERE a_expr { $$ = $2; }
| /*EMPTY*/ { $$ = NULL; }
;
// 匹配 id in (select a from t2);
a_expr: c_expr { $$ = $1; }
| a_expr IN_P in_expr
{
/* in_expr returns a SubLink or a list of a_exprs */
if (IsA($3, SubLink))
{
/* generate foo = ANY (subquery) */
SubLink *n = (SubLink *) $3;
n->subLinkType = ANY_SUBLINK;
n->subLinkId = 0;
n->testexpr = $1; // 具体到上面的SQL语句是id column
n->operName = NIL; /* show it's IN not = ANY */
n->location = @2;
$$ = (Node *)n;
}
else
{
/* generate scalar IN expression */
$$ = (Node *) makeSimpleA_Expr(AEXPR_IN, "=", $1, $3, @2);
}
}
// 匹配 (select a from t2)
in_expr: select_with_parens
{
SubLink *n = makeNode(SubLink);
n->subselect = $1;
/* other fields will be filled later */
$$ = (Node *)n;
}
| '(' expr_list ')' { $$ = (Node *)$2; }
;
我们知道 "x IN (select)", convert to "x = ANY (select)"
这个转换是等价的。在解析阶段,会进行 IN
转 ANY
的处理,在调用过程如下:transformSelectStmt
->transformWhereClause
->transformExpr
->transformSubLink
。在 transformSublink
中进行这个转换处理:
static Node *transformSubLink(ParseState *pstate, SubLink *sublink)
{
Node *result = (Node *) sublink;
Query *qtree;
// 省略中间代码......
/* If the source was "x IN (select)", convert to "x = ANY (select)".*/
if (sublink->operName == NIL)
sublink->operName = list_make1(makeString("="));
// 省略中间代码......
return result;
}
理解了子连接在解析层的表示后,我们重点分析一下逻辑优化阶段的处理。
逻辑优化
逻辑优化的基础是关系代数的等价变换,等价推理。有点类似于中学时数学中的简便运算思想。最容易理解的优化思想是:先选择后投影&