一条SQL在He3DB中会经过逻辑优化阶段,这里我们分析一下其中的一个逻辑优化过程,常量表达式化简。对此进行分析。
常量表达式化简
常量表达式可以进行化简,可降低执行器计算表达式的代价。在逻辑优化阶段,会判断是否可以进行常量表达式化简,如果可以,则在执行器执行之前就预先对常量表达式树进行计算,计算出常量后,以新计算出的常量表达式代替原有的表达式。当进入执行器时,此时表达式已被替换为常量,避免了在执行器中频繁的计算表达式。
化简示例
我们以下面的SQL为例,条件表达式为a > 1 + 1,是常量表达式,可以进行化简。
-- 常量表达式可以进行化简
postgres=# explain select * from t1 where a > 1 + 1;
QUERY PLAN
-----------------------------------------------------
Seq Scan on t1 (cost=0.00..17.50 rows=998 width=8)
Filter: (a > 2) -- 进入执行器中, 表达式已被提前替换为常量,每扫描一个元组,不用再进行1+1的表达式计算了
(2 rows)
-- 不满足常量表达式化简的条件
postgres=# explain select * from t1 where a > 1 + random();
QUERY PLAN
------------------------------------------------------------------------
Seq Scan on t1 (cost=0.00..25.00 rows=333 width=8)
Filter: ((a)::double precision > ('1'::double precision + random())) -- 执行器中每扫描一个元组,都要进行一次表达式计算
(2 rows)
除了常量表达式,常量函数也可以在逻辑优化阶段提前执行,计算得到常量,用常量替换原有的函数表达式。
源码分析
我们以select * from t1 where a = 1 + 1;这条语句为例,分析一下在He3DB中是如何进行化简的。主流程如下
exec_simple_query
--> pg_parse_query
--> raw_parser
--> base_yyparse
--> pg_analyze_and_rewrite
--> transformStmt
--> transformSelectStmt
--> transformWhereClause
--> transformExpr
--> pg_plan_queries
--> pg_plan_query
--> planner
--> standard_planner
--> subquery_planner
--> preprocess_qual_conditions
--> preprocess_expression
--> eval_const_expressions // 在这里完成常量表达式化简,完成1+1表达式替换为常量2
--> eval_const_expressions_mutator
--> simplify_function
--> evaluate_function
--> evaluate_expr // 1+1的表达式被计算为常量2
--> create_plan
--> PortalStart
--> PortalRun // 执行器执行
--> PortalDrop
可以看到,在逻辑优化阶段,常量表达式已被化简为常量,进入执行器时,表达式树已被替换为常量。
/* evaluate_expr: pre-evaluate a constant expression*/
Expr * evaluate_expr(Expr *expr, Oid result_type, int32 result_typmod, Oid result_collation)
{
EState *estate;
ExprState *exprstate;
MemoryContext oldcontext;
Datum const_val;
bool const_is_null;
int16 resultTypLen;
bool resultTypByVal;
/*
* To use the executor, we need an EState.
*/
estate = CreateExecutorState();
/* We can use the estate's working context to avoid memory leaks. */
oldcontext = MemoryContextSwitchTo(estate->es_query_cxt);
/* Make sure any opfuncids are filled in. */
fix_opfuncids((Node *) expr);
/*
* Prepare expr for execution. (Note: we can't use ExecPrepareExpr
* because it'd result in recursively invoking eval_const_expressions.)
*/
exprstate = ExecInitExpr(expr, NULL);
/*
* And evaluate it.
*
* It is OK to use a default econtext because none of the ExecEvalExpr()
* code used in this situation will use econtext. That might seem
* fortuitous, but it's not so unreasonable --- a constant expression does
* not depend on context, by definition, n'est ce pas?
*/
const_val = ExecEvalExprSwitchContext(exprstate,
GetPerTupleExprContext(estate),
&const_is_null); // 计算表达式 1 + 1
/* Get info needed about result datatype */
get_typlenbyval(result_type, &resultTypLen, &resultTypByVal);
/* Get back to outer memory context */
MemoryContextSwitchTo(oldcontext);
/*
* Must copy result out of sub-context used by expression eval.
*
* Also, if it's varlena, forcibly detoast it. This protects us against
* storing TOAST pointers into plans that might outlive the referenced
* data. (makeConst would handle detoasting anyway, but it's worth a few
* extra lines here so that we can do the copy and detoast in one step.)
*/
if (!const_is_null)
{
if (resultTypLen == -1)
const_val = PointerGetDatum(PG_DETOAST_DATUM_COPY(const_val));
else
const_val = datumCopy(const_val, resultTypByVal, resultTypLen);
}
/* Release all the junk we just created */
FreeExecutorState(estate);
/*
* Make the constant result node.
*/
return (Expr *) makeConst(result_type, result_typmod, result_collation,
resultTypLen,
const_val, const_is_null,
resultTypByVal);
}
「喜欢这篇文章,您的关注和赞赏是给作者最好的鼓励」
关注作者
【版权声明】本文为墨天轮用户原创内容,转载时必须标注文章的来源(墨天轮),文章链接,文章作者等基本信息,否则作者和墨天轮有权追究责任。如果您发现墨天轮中有涉嫌抄袭或者侵权的内容,欢迎发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。




