第三章 查询处理
2021-10-11 15:10:14 0 举报
AI智能生成
postgreSQL查询处理
作者其他创作
大纲/内容
3.1 概览<br>
3.1.1 解析器(Parser)<br>
语法解析树结构体
typedef struct SelectStmt<br>{<br> NodeTag type;<br><br> /* 这些字段只会在SelectStmts“叶节点”中使用 */<br> List *distinctClause; /* NULL, DISTINCT ON表达式列表, 或<br> 对所有的(SELECT DISTINCT)为lcons(NIL,NIL) */<br> IntoClause *intoClause; /* SELECT INTO 的目标 */<br> List *targetList; /* 结果目标列表 (ResTarget) */<br> List *fromClause; /* FROM 子句 */<br> Node *whereClause; /* WHERE 限定条件 */<br> List *groupClause; /* GROUP BY 子句 */<br> Node *havingClause; /* HAVING 条件表达式 */<br> List *windowClause; /* WINDOW window_name AS (...), ... */<br><br> /* 在一个表示值列表的叶节点中,上面的字段全都为空,而这个字段会被设置。<br> * 注意这个子列表中的元素仅仅是表达式,没有ResTarget的修饰,还需要注意列表元素可能为<br> * DEFAULT (表示一个 SetToDefault 节点),而无论值列表的上下文。 <br> * 由分析阶段决定否合法并拒绝。 */<br> List *valuesLists; /* 未转换的表达式列表 */<br><br> /* 这些字段会同时在SelectStmts叶节点与SelectStmts上层节点中使用 */<br> List *sortClause; /* 排序子句 (排序依据的列表) */<br> Node *limitOffset; /* 需要跳过的元组数目 */<br> Node *limitCount; /* 需要返回的元组数目 */<br> List *lockingClause; /* FOR UPDATE (锁子句的列表) */<br> WithClause *withClause; /* WITH 子句 */<br><br> /* 这些字段只会在上层的 SelectStmts 中出现 */<br> SetOperation op; /* set 操作的类型 */<br> bool all; /* 是否指明了 ALL 选项? */<br> struct SelectStmt *larg; /* 左子节点 */<br> struct SelectStmt *rarg; /* 右子节点 */<br>} SelectStmt;
解析器根据SQL语句生成一颗语法解析树(parse tree)
<br>
3.1.2 分析器(Analyzer)<br>
分析器数据结构<br>
/*<br> * Query -<br> * 解析与分析过程会将所有的语句转换为一颗查询树,供重写器与计划器用于进一步的处理。<br> * 功能语句(即不可优化的语句)会设置utilityStmt字段,而Query结构本身基本上是空的。<br> * DECLARE CURSOR 是一个特例:它的形式与SELECT类似,但原始的DeclareCursorStmt会<br> * 被放在 utilityStmt 字段中。<br> * 计划过程会将查询树转换为一颗计划树,计划树的根节点是一个PlannedStmt结构<br> * 执行器不会用到查询树结构<br> */<br>typedef struct Query<br>{<br> NodeTag type;<br> CmdType commandType; /* select|insert|update|delete|utility */<br> QuerySource querySource; /* 我来自哪里? */<br> uint32 queryId; /* 查询标识符 (可由插件配置) */<br><br> bool canSetTag; /* 我设置了命令结果标签吗? */<br> Node *utilityStmt; /* 如果这是一条DECLARE CURSOR或不可优化的语句 */<br> int resultRelation; /* 对增删改语句而言是目标关系的索引; SELECT为0 */<br> bool hasAggs; /* 是否在目标列表或having表达式中指定了聚合函数 */<br> bool hasWindowFuncs; /* tlist是否包含窗口函数 */<br> bool hasSubLinks; /* 是否包含子查询SubLink */<br> bool hasDistinctOn; /* 是否包含来自DISTINCT ON的distinct子句 */<br> bool hasRecursive; /* 是否制定了WITH RECURSIVE */<br> bool hasModifyingCTE; /* 是否在WITH子句中包含了INSERT/UPDATE/DELETE */<br> bool hasForUpdate; /* 是否指定了FOR [KEY] UPDATE/SHARE*/<br> bool hasRowSecurity; /* 是否应用了行安全策略 */<br> List *cteList; /* CTE列表 */<br> List *rtable; /* 范围表项目列表 */<br> FromExpr *jointree; /* 表连接树 (FROM 与 WHERE 子句) */<br> List *targetList; /* 目标列表 (TargetEntry的列表) */<br> List *withCheckOptions; /* WithCheckOption的列表 */<br> OnConflictExpr *onConflict; /* ON CONFLICT DO [NOTHING | UPDATE] */<br> List *returningList; /* 返回值列表(TargetEntry的列表) */<br> List *groupClause; /* SortGroupClause的列表 */<br> List *groupingSets; /* 如果有,GroupingSet的列表 */<br> Node *havingQual; /* 分组的Having条件列表 */<br> List *windowClause; /* 窗口子句列表 */<br> List *distinctClause; /* SortGroupClause列表 */<br> List *sortClause; /* SortGroupClause列表 */<br> Node *limitOffset; /* Offset跳过元组数目 (int8 表达式) */<br> Node *limitCount; /* Limit返回元组数目 (int8 表达式) */<br> List *rowMarks; /* RowMarkClause列表 */<br> Node *setOperations; /* 如果是UNION/INTERSECT/EXCEPT的顶层查询,<br> 则为集合操作列表 */<br> List *constraintDeps; /* 确认查询语义是否合法时,所依赖约束对象的OID列表 */<br>} Query;
分析器对语法解析树进行语义分析,生成一颗查询树(query tree)
3.1.3 重写器(Rewriter)<br>
重写器按照规则系统中存在的规则,对查询树进行改写。
重写器结构体<br>
子主题
3.1.4 计划器与执行器<br>
计划器基于查询树,生成一颗执行效率最高的计划树(plan tree)
执行器按照计划树中的顺序访问表和索引,执行相应查询。
计划器结构体<br>
执行器结构体<br>
3.2 单表查询的代价估计<br>
3.2.1 顺序扫描<br>
顺序扫描的代价是通过函数cost_seqscan()估计的。
3.2.2 索引扫描<br>
尽管PostgreSQL支持很多索引方法,比如B树,GiST,GIN和BRIN,不过索引扫描的代价估计都使用一个共用的代价函数:cost_index()。
3.2.3 排序<br>
排序路径(sort path) 会在排序操作中被使用。排序操作包括ORDER BY,归并连接的预处理操作,以及其他函数。函数cost_sort()用于估计排序操作的代价。
代价<br>
PostgreSQL的查询优化是基于代价(Cost)的。代价是一个无量纲的值,它并不是一种绝对的性能指标,但可以作为比较各种操作代价时的相对性能指标。<br><br>costsize.c中的函数用于估算各种操作的代价。所有被执行器执行的操作都有着相应的代价函数。例如,函数cost_seqscan() 和 cost_index()分别用于估算顺序扫描和索引扫描的代价。<br><br>在PostgreSQL中有三种代价:启动(start-up) , 运行(run)和总和(total)。总代价是启动代价和运行代价的和;因此只有启动代价和运行代价是单独估计的。<br><br> 启动代价(start-up):在读取到第一条元组前花费的代价,比如索引扫描节点的启动代价就是读取目标表的索引页,取到第一个元组的代价<br> 运行代价(run): 获取全部元组的代价<br> 总代价(total):前两者之和
3.3 创建单表查询的计划树<br>
3.3.1 预处理<br>
简化目标列表(target list),LIMIT子句等;<br><br>例如,表达式2+2会被重写为4,这是由clauses.c中eval_const_expressions()函数负责的。<br><br>布尔表达式的规范化<br><br>例如,NOT(NOT a)会被重写为a<br><br>压平与/或表达式<br><br>SQL标准中的AND/OR是二元操作符;但在PostgreSQL内部它们是多元操作符。而计划器总是会假设所有的嵌套AND/OR都应当被压平。
3.3.2 找出代价最小的访问路径<br>
创建一个RelOptInfo数据结构,存储访问路径及其代价。<br><br> RelOptInfo结构体是通过make_one_rel()函数创建的,并存储于PlannerInfo结构体的simple_rel_array字段中,如图3.10所示。在初始状态时RelOptInfo持有着baserestrictinfo变量,如果存在相应索引,还会持有indexlist变量。baserestrictinfo存储着查询的WHERE子句,而indexlist存储着目标表上相关的索引。<br><br> typedef enum RelOptKind<br> {<br> RELOPT_BASEREL,<br> RELOPT_JOINREL,<br> RELOPT_OTHER_MEMBER_REL,<br> RELOPT_UPPER_REL,<br> RELOPT_DEADREL<br> } RelOptKind;<br><br> typedef struct RelOptInfo<br> {<br> NodeTag type;<br> RelOptKind reloptkind;<br><br> /* 本RelOptInfo包含的所有关系 */<br> Relids relids; /* 基本关系的ID集合 (范围表索引) */<br><br> /* 由计划器生成的预估尺寸 */<br> double rows; /* 预估结果元组数目 */<br><br> /* 计划器标记位,每个关系一份 */<br> bool consider_startup; /* 保留启动代价最低的路径? */<br> bool consider_param_startup; /* 同上, 针对参数化路径? */<br> bool consider_parallel; /* 考虑并行路径? */<br><br> /* 扫描当前关系的默认结果目标列表 */<br> struct PathTarget *reltarget; /* Vars/Exprs, 代价, 宽度的列表 */<br><br> /* 物化相关信息 */<br> List *pathlist; /* Path 结构体列表 */<br> List *ppilist; /* pathlist中使用的ParamPathInfos */<br> List *partial_pathlist; /* 部分路径 */<br> struct Path *cheapest_startup_path;<br> struct Path *cheapest_total_path;<br> struct Path *cheapest_unique_path;<br> List *cheapest_parameterized_paths;<br><br> /* 基础关系与连接关系都需要的 参数化信息 */<br> /* (参见 lateral_vars 与 lateral_referencers) */<br> Relids direct_lateral_relids; /* 直接以LATERAL方式引用的关系 */<br> Relids lateral_relids; <br><br> /* 关于基础关系的信息 (连接关系不会设置这些字段!) */<br> Index relid;<br> Oid reltablespace; /* 表空间 */<br> RTEKind rtekind; /* RELATION, SUBQUERY, 或 FUNCTION */<br> AttrNumber min_attr; /* 关系属性的最小值 (通常<0) */<br> AttrNumber max_attr; /* 关系属性的最大值 */<br> Relids *attr_needed; /* 被索引的数组 [min_attr .. max_attr] */<br> int32 *attr_widths; /* 被索引的数组 [min_attr .. max_attr] */<br> List *lateral_vars; /* 关系所引用的LATERAL Vars 与 PHVs */<br> Relids lateral_referencers;/* 侧面引用本表的关系 */<br> List *indexlist; /* IndexOptInfo列表 */<br> BlockNumber pages; /* 来自pg_class的页面数估计值 */<br> double tuples;<br> double allvisfrac;<br> PlannerInfo *subroot; /* 如有子查询 */<br> List *subplan_params; /* 如有子查询 */<br> int rel_parallel_workers; /* 期望的并行工作进程数量 */<br><br> /* 有关外部表与外部表连接的相关信息 */<br> Oid serverid; /* 外部表与外部表连接相应的服务器ID */<br> Oid userid; /* 用于检查访问权限的用户标识 */<br> bool useridiscurrent;/* 当前用户是否能合法进行JOIN */<br> struct FdwRoutine *fdwroutine;<br> void *fdw_private;<br><br> /* 被各种扫描与连接所使用 */<br> List *baserestrictinfo; /* RestrictInfo结构体列表 (如果存在基础关系) */<br> QualCost baserestrictcost; /* 求值上述限制条件的代价 */<br> List *joininfo; /* RestrictInfo 结构体列表,涉及到本表的连接会用到 */<br> bool has_eclass_joins; /* T 意味着joininfo不完整 */<br> } RelOptInfo;<br><br> 估计所有可能访问路径的代价,并将访问路径添加至RelOptInfo结构中。<br><br> 这一处理过程的细节如下:<br> 创建一条路径,估计该路径中顺序扫描的代价,并将其写入路径中。将该路径添加到RelOptInfo结构的pathlist变量中。<br> 如果目标表上存在相关的索引,则为每个索引创建相应的索引访问路径。估计所有索引扫描的代价,并将代价写入相应路径中。然后将索引访问路径添加到pathlist变量中。<br> 如果可以进行位图扫描,则创建一条位图扫描访问路径。估计所有的位图扫描的代价,并将代价写入到路径中。然后将位图扫描路径添加到pathlist变量中。<br><br> 从RelOptInfo的pathlist中,找出代价最小的访问路径。<br><br> 如有必要,估计LIMIT,ORDER BY和AGGREGATE操作的代价。
3.3.3 创建计划树<br>
在最后一步中,计划器按照代价最小的路径生成一颗计划树。 <br><br>计划树的根节点是定义在plannodes.h中的PlannedStmt结构,包含19个字段,其中有4个代表性字段:<br><br> **commandType**存储操作的类型,诸如SELECT,UPDATE和INSERT。<br> **rtable**存储范围表的列表(RangeTblEntry的列表)。<br> **relationOids**存储与查询相关表的oid。<br> **plantree**存储着一颗由计划节点组成的计划树,每个计划节点对应着一种特定操作,诸如顺序扫描,排序和索引扫描。
PlanNode是所有计划节点的基类,其他计划节点都会包含PlanNode结构。比如顺序扫描节点SeqScanNode,包含一个PlanNode和一个整型变量scanrelid。PlanNode包含14个字段。下面是7个代表性字段:<br><br> startup_cost和total_cost是该节点对应操作的预估代价。<br> rows是计划器预计扫描的行数。<br> targetlist保存了该查询树中目标项的列表。<br> qual储存了限定条件的列表。<br> lefttree和righttree用于添加子节点。
3.4 执行器如何工作<br>
临时文件<br>
执行器在处理查询时会使用工作内存(work_mem)和临时缓冲区(temp_buffers),两者都于内存中分配。如果查询无法在内存中完成,就会用到临时文件。
3.5 连接<br>
3.5.1 嵌套循环连接<br>
<br>
3.5.2 归并连接<br>
<br>
3.5.3 散列连接<br>
<br>
3.5.4 连接访问路径与连接节点<br>
<br>
3.6 创建多表查询计划树<br>
3.6.1 预处理<br>
3.6.2 获取代价最小的路径<br>
3.6.3 获取三表查询代价最小的路径<br>
0 条评论
下一页