PostgreSQL——查询优化——生成路径1
Posted weixin_47373497
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了PostgreSQL——查询优化——生成路径1相关的知识,希望对你有一定的参考价值。
2021SC@SDUSC
目录
概述
我负责的PostgreSQL代码部分:查询的编译与执行
此篇博客的分析内容:查询优化——生成路径
查询优化的整个过程可以分为预处理,生成路径和生成计划三个阶段。在上一篇博客中,我分析了查询优化的第一个阶段——预处理。这篇博客分析查询优化的第二个阶段——生成路径。在关系数据库中和非关系数据库对比,最大的特色是支持表的连接。但是表的连接结果会因连接顺序的不同而不同,所以同一组表的连接结果可能有多个。表的连接在逻辑上,我们可以看做是一棵连接树,每一棵连接树在PostgreSQL中都被称作是一条路径。因为同一组表的连接结果会有多条路径,所以查询优化的工作是:从一系列等效路径中选取效率最高的路径并形成执行计划
query_planner函数
生成路径的工作是由query_planner函数来完成的。为一个基本的查询(可能涉及连接)生成访问路径
query_planner函数执行流程
query_planner(PlannerInfo *root,
query_pathkeys_callback qp_callback, void *qp_extra)
Query *parse = root->parse;//查询树
List *joinlist;//连接表
RelOptInfo *final_rel;//最后生成的路径结果存放在RelOptInfo结构体中返回
//初始化planner中的相关变量
root->join_rel_list = NIL;
root->join_rel_hash = NULL;
root->join_rel_level = NULL;
root->join_cur_level = 0;
root->canon_pathkeys = NIL;
root->left_join_clauses = NIL;
root->right_join_clauses = NIL;
root->full_join_clauses = NIL;
root->join_info_list = NIL;
root->placeholder_list = NIL;
root->fkey_list = NIL;
root->initial_rels = NIL;
//调用setup_simple_rel_arrays函数将范围表扁平化,以便更快地访问,并为索引基本关系设置一个空数组。
setup_simple_rel_arrays(root);
//判断jointree->fromlist是否为空,即该表是否为简单表而不是连接表
Assert(parse->jointree->fromlist != NIL);
//处理fromlist字段只有一个表的情况即无表连接
if (list_length(parse->jointree->fromlist) == 1)
//取出连接表的fromlist字段,并将其转化为node节点形式
Node *jtnode = (Node *) linitial(parse->jointree->fromlist);
if (IsA(jtnode, RangeTblRef))//取出连接表的fromlist字段是否为范围表即from子句
int varno = ((RangeTblRef *) jtnode)->rtindex;//获取该节点在r_table即范围表中的位置
RangeTblEntry *rte = root->simple_rte_array[varno];//根据上一步获得的r_table的位置,取出该表的RTE
Assert(rte != NULL);//判断RTE是否为空
if (rte->rtekind == RTE_RESULT)//判断RTE的种类是否为空的from子句
//为查询中的基本表关系构建RelOptInfo节点
final_rel = build_simple_rel(root, varno, NULL);
基本表就是 FROM 后指定的一个普通的表,而不是由连接等操作生成的表。
//如果查询在一般情况下允许并行性,检查是否有并行限制。
if (root->glob->parallelModeOK &&
force_parallel_mode != FORCE_PARALLEL_OFF)
final_rel->consider_parallel =
is_parallel_safe(root, parse->jointree->quals);//判断是否是安全的并行
//添加访问路径
add_path(final_rel, (Path *)
create_group_result_path(root, final_rel,
final_rel->reltarget,
(List *) parse->jointree->quals));
//选择最优的访问路径,并将最优路径放在最后返回的PlannerInfo结构体中
set_cheapest(final_rel);
//回调函数
(*qp_callback) (root, qp_extra);
//返回的PlannerInfo结构体中存放的最优路径
return final_rel;
//处理fromlist字段>1的情况即有多个表的连接的情况
setup_append_rel_array(root);//用每个AppendRelInfo填充append rel数组,允许通过子relid直接查找
//在 add_base_rels_to_query() 中初始化每个 RelOptInfo
add_base_rels_to_query(root, (Node *) parse->jointree);
//检查targetlist和join树,为所有引用的占位符添加条目到baserel targetlists,并为所有引用的占位符生成占位信息条目。
build_base_rel_tlists(root, root->processed_tlist);
//查找级联引用
find_lateral_references(root);
//deconstruct_jointree() 调用完成后,会返回一个上述的连接链表,同时针对每个基本表的扫描或连接限制条件,都存入了各自的 RelOptInfo 的 baserestrictinfo/joininfo 中
joinlist = deconstruct_jointree(root);
//处理隐含的等式条件
generate_base_implied_equalities(root);
//规范化pathkeys
(*qp_callback) (root, qp_extra);
//移除没有用上的外部连接
joinlist = remove_useless_joins(root, joinlist);
//将任何具有唯一内部关系的半连接减少为普通的内部连接
reduce_unique_semijoins(root);
//构建级联参考
create_lateral_join_info(root);
// 将外键与等价类相匹配并添加配额。这一步必须在最终确定等价类后进行,这样就可以跳过处理参与删除关系的外键。
match_foreign_keys_to_quals(root);
//提取or子句的限制条件
extract_restriction_or_clauses(root);
//现在通过添加其他表来扩展
add_other_rels_to_query(root);
//调用make_one_rel函数返回查询路径
final_rel = make_one_rel(root, joinlist);
//检查是否得到可用的路径
if (!final_rel || !final_rel->cheapest_total_path ||
final_rel->cheapest_total_path->param_info != NULL)
//没有可用路径则报错
elog(ERROR, "failed to construct the join relation");
//返回的PlannerInfo结构体中存放的最优路径
return final_rel;
RelOptInfo结构体
RelOptInfo结构体是贯穿整个路径生成过程的一个数据结构,生成路径的最终结果始终存放其中,生成和选择路径所需的许多数据也存放在其中。路径生成和选择涉及的所有操作,几乎都是针对RelOptInfo结构体操作。
RelOptInfo结构体涉及baserel(基本关系)和joinrel(连接关系)。baserel(基本关系)是一个普通表,子查询或者范围表中出现的函数。joinrel(连接关系)是两个或者两个以上的baserel(基本关系)在一定约束条件下的简单合并。对任何一组baserel仅有一个joinrel,即对于任何给定的baserel集合,只有一个RelOptInfo结构体。例如:连接a,b,c是用同一个RelOptInfo结构体表示,无论a,b,c这三个baserel连接的顺序是什么样的。因为对于joinrel(连接关系)来说,baserel之间是没有顺序的。至于这三个baserel之间是以什么样的路径(连接顺序,方式)连接成a,b,c形式的连接,则记录在RelOptInfo结构体中的pathlist字段中。
typedef struct RelOptInfo
NodeTag type;//节点类型
RelOptKind reloptkind;//关系的类型(基本关系类型,连接关系类型,其他成员关系类型)
Relids relids; //关系所涉及的基本表(在范围表中的编号)
double rows; //估计的元组数量
bool consider_startup; //是否考虑启动成本?是,需要保留启动成本低的路径
bool consider_param_startup; //是否考虑参数化?的路径
bool consider_parallel; //是否考虑并行处理路径
List *pathlist; //多个基本关系生成该关系的路径
List *ppilist; //路径链表中的ParamPathInfos链表
List *partial_pathlist; //并行部分路径
struct Path *cheapest_startup_path;//最小的启动代价的路径
struct Path *cheapest_total_path;//最小的总代价的路径
struct Path *cheapest_unique_path;//最小的唯一代价的路径
List *cheapest_parameterized_paths; //参数化代价最低的路径
Relids direct_lateral_relids; //使用lateral语法,需依赖的Relids
Relids lateral_relids; //rel的最小化参数信息
Index relid;//当前关系在范围表中的编号
Oid reltablespace; /* containing tablespace */
RTEKind rtekind; //基本关系的类型(普通关系,子查询,函数等)
AttrNumber min_attr; //关系中最小的字段编号
AttrNumber max_attr; //关系中最大的字段编号
Relids *attr_needed; //连接关系中哪些字段是需要的
int32 *attr_widths; //每个字段的宽度的估计
List *lateral_vars; //关系依赖的LATERAL Vars/PHVs
Relids lateral_referencers; //依赖该关系的Relids
List *indexlist; //索引关系的信息
List *statlist; //统计信息链表
BlockNumber pages; //关系占用的页面数
double tuples; //关系的元组数
PlannerInfo *subroot; //如为子查询,存储子查询的root
List *subplan_params; //如为子查询,存储子查询的参数
int rel_parallel_workers; //并行执行,需要多少个workers,记录workers数量
Oid serverid; //表或连接的服务器标识符
Oid userid; //用户id标识
bool useridiscurrent; //标识当前用户,对于当前用户来说,连接才是有效的
struct FdwRoutine *fdwroutine; //使用结构体FdwRoutine,避免包含头文件fdwapi.h
List *unique_for_rels; //已知的,可保证唯一的Relids链表
List *non_unique_for_rels; //已知的,不唯一的Relids链表
List *baserestrictinfo; //如为基本关系,存储约束条件链表
QualCost baserestrictcost; //解析约束表达式的成本
Index baserestrict_min_security; //在baserestrictinfo中最低安全等级
List *joininfo; //连接子句中调用该关系时的约束信息
bool has_eclass_joins; //是否存在等值连接
bool consider_partitionwise_join; //是否考虑使用partitionwise join
Relids top_parent_relids; //最高层的父关系Relids
PartitionScheme part_scheme; //分区的schema
int nparts; //分区数
struct PartitionBoundInfoData *boundinfo; //分区边界信息
List *partition_qual; //分区约束
struct RelOptInfo **part_rels; //分区的RelOptInfo数组
List **partexprs; //非空分区键表达式链表
List **nullable_partexprs; //可为空的分区键表达式
List *partitioned_child_rels; // 分区子表RT Indexes链表
RelOptInfo;
Path结构体
在上面介绍的RelOptInfo中,变量pathlist记录的是生成该RelOptInfo在某方面较优的路径,其中路径中每一个节点都是一个path结构,path结构也成为路径。在pathlist中,每一个path节点对应的具体路径节点则存放的是构成该路径的具体信息,包括表连接的方式,顺序,以及参加连接的baserel的访问方式。
typedef struct Path
NodeTag type; //节点类型
NodeTag pathtype; //标记扫描/连接方法
RelOptInfo *parent; //此路径所构建的关系
double rows; //估计的结果元组的数量
Cost startup_cost; //执行此路径的启动代价估计
Cost total_cost; //执行此路径的总代价估计
List *pathkeys; //此路径的输出的排序信息
Path;
query_planner函数调用的——make_one_rel函数
make_one_rel函数找出执行查询的所有可能访问路径。make_one_rel函数分为两个阶段:生成扫描路径(set_base_rel_pathlists)和生成连接路径(make_rel_from_joinlist).
make_one_rel(PlannerInfo *root, List *joinlist)
//传入参数List *joinlist:这个链表中每一个节点都代表一个基本关系,该基本关系可能表示一个范围表或者是一个子查询
RelOptInfo *rel;//RelOptInfo结构体,存储最终路径以及路径查找的相关信息
Index rti;//记录当前关系在范围表中的编号
double total_pages; //关系占用的页面数
//构建All_baserels Relids集合
root->all_baserels = NULL;//初始将它置空
for (rti = 1; rti < root->simple_rel_array_size; rti++)//遍历RelOptInfo
RelOptInfo *brel = root->simple_rel_array[rti];//取得关系
//检查上一步获得的brel即获取的关系是否为空
if (brel == NULL)
continue;
//检查该关系是否在范围表中
Assert(brel->relid == rti);
//如果不是brel的reloptkind字段的类型不是基本表则忽略(说明该关系是连接关系而不是基本关系)
if (brel->reloptkind != RELOPT_BASEREL)
continue;
//如果是基本表的话,则将它加入基本表链表中
root->all_baserels = bms_add_member(root->all_baserels, brel->relid);
//设置RelOptInfo的consider_param_startup变量,是否考量fast-start plans
set_base_rel_consider_startup(root);
//估算Relation的Size并且设置consider_parallel标记
set_base_rel_sizes(root);
以上是关于PostgreSQL——查询优化——生成路径1的主要内容,如果未能解决你的问题,请参考以下文章