引言
在上篇博客中我对查询优化optimizer进行了简单概述。在本篇博客中,我将对查询优化相关的主调函数的调用情况进行简要解读。
exec_simple_query()
这个函数位于通信管理模块tcop(/src/gausskernel/process/tcop
)下的文件postgres.cpp中,它由Postgres服务线程调用,用来完成对用户输入的“简单查询语句”的执行工作。
/*
* exec_simple_query
*
* Execute a "simple Query" protocol message.
*/
static void exec_simple_query(const char* query_string, MessageType messageType, StringInfo msg = NULL)
可以看到,在代码line 2504~2507的位置,函数调用pg_analyze_and_rewrite() 完成对用户输入的查询语句的解析和重写工作。
if (HYBRID_MESSAGE != messageType)
querytree_list = pg_analyze_and_rewrite(parsetree, query_string, NULL, 0);
else
querytree_list = pg_analyze_and_rewrite(parsetree, sql_query_string, NULL, 0);
其中传入的参数parsetree是已经初始化的解析树结点,它的类型是Node*。
Node* parsetree = (Node*)lfirst(parsetree_item);
函数pg_analyze_and_rewrite调用完成后,我们就得到了已经解析并应用重写规则重写后的查询树列表,保存在querytree_list中,它是一个List* 类型的变量。
List* querytree_list = NULL;
在查询解析和重写工作完成之后,其最终产物——查询树链表将被移交给查询规划模块,该模块的入口函数是pg_plan_queries,其调用位置在函数代码的line 2559位置。这个函数负责将查询树链表变成执行计划链表。pg_plan_queries 只会处理非Utility命令,它调用pg_plan_query对每一个查询树进行处理,并将生成的 PlannedStmt结构体(上篇博客中有简单介绍)构成一个链表(执行计划链表)返回。
plantree_list = pg_plan_queries(querytree_list, 0, NULL);
执行计划链表保存在plantree_List中,它也是一个List* 类型的变量。
List* plantree_list = NULL;
pg_plan_queries()
重要部分源码及注释如下:
foreach (query_list, querytrees) {
Query* query = castNode(Query, lfirst(query_list));
Node* stmt = NULL;
PlannedStmt* ps = NULL;
if (query->commandType == CMD_UTILITY) {
/* Utility commands have no plans. */
stmt = query->utilityStmt;
/* output grammer error of hint */
output_utility_hint_warning((Node*)query, DEBUG1);
} else {
query->boundParamsQ = boundParams;
bool is_insert_multiple_values = is_insert_multiple_values_query_in_gtmfree(query);
/* Temporarily apply SET hint using PG_TRY for later recovery */
int nest_level = apply_set_hint(query);
PG_TRY();
{
stmt = (Node*)pg_plan_query(query, cursorOptions, boundParams);
}
PG_CATCH();
{
recover_set_hint(nest_level);
PG_RE_THROW();
}
PG_END_TRY();
……
如前面所说,pg_plan_queries 只会处理非Utility命令(包括notify,alter,copy等),如果传入的查询树链表中包括Utility命令,它会向外抛出一个警告,并且不为这条查询语句生成执行计划。如果是non-Utility命令(包括SELECT/INSERT/UPDATE/DELETE/MERGE),则调用pg_plan_query对查询树进行处理,并将生成的 PlannedStmt结构体(构成一个链表(执行计划链表)返回。
pg_plan_query()
函数头部注释:
/*
* Generate a plan for a single already-rewritten query.
* This is a thin wrapper around planner() and takes the same parameters.
*/
本函数为一个已经完成重写的查询生成执行计划。pg_plan_query中负责实际计划生成的是planner函数,并且通常认为从planner函数开始就进入了执行计划的生成阶段。函数是对planner函数的一个包装函数,并且它们的参数几乎相同。对于optimizer的入口函数planner,上篇博客中有简单介绍。
/* call the optimizer */
plan = planner(querytree, cursorOptions, boundParams);
对planner函数的调用结果保存在变量plan中,它是PlannedStmt* 类型的。在调用完成后,经过对生成的执行计划的检查和必要的输出工作(Print plan if debugging)之后,将执行计划返回给pg_plan_queries函数。
return plan;
standard_planner()
在上篇博客中我们介绍过,函数planner在大多数情况下会调用standard_planner来进行实际的查询优化。standard_planner的返回值是一个PlannerStmt结构,其中包含执行器执行计划时的全部信息,包括计划树(Plan)、子计划树和相关参数信息。
PlannedStmt* standard_planner(Query* parse, int cursorOptions, ParamListInfo boundParams)
函数standard_planner位于planner.cpp中,其有三个入口参数:
-
parse:一个Query结构,表示需要处理的查询树。
-
cursorOption:整型,表示游标选项,在处理与游标操作时用到。
-
ParamListInfo 结构,记录了所用到的参数信息。
至于实际将查询树转化为计划树的工作standard_planner通过调用subquery_planner函数来实现。subquery_planner接收Query查询树,返回Plan (计划树),该计划树被包装在PlannedStmt中。对含有子查询的情况,可通过递归调用为子查询生成相应的子计划并链接到上层计划。该函数主要调用相关预处理函数,依据消除冗余条件、减少递归层数、简化路径生成等原则对Query查询树进行预处理。
/* primary planning entry point (may recurse for subqueries) */
top_plan = subquery_planner(glob, parse, NULL, false, tuple_fraction, &root);
对于函数subquery_planner在下一篇博客中会进行介绍。
函数 | 功能描述 |
---|---|
exec_simple_query | 执行用户输入的简单查询语句。 |
pg_plan_queries | 将查询树链表变成执行计划链表。调用pg_plan_query以完成单个查询的转化。 |
pg_plan_query | 将单个查询树转化成计划树。是planner的包装函数。 |
planner | optimizer入口函数。输入查询树,输出计划树。 |
standard_planner | 标准的规划处理。输入查询树,输出计划树。 |
subquery_planner | 优化处理的主体函数。可以递归处理子查询。 |
总结
在本篇博客中我对optimizer的主调函数进行了简单介绍。下一篇博客中我会对函数 subquery_planner() 进行学习和解读工作。