PostgreSQL查询语句执行过程中的主要函数源代码分析

SELECT * FROM shoelace

查询主函数 exec_simple_query

后台服务器收到查询后,在PostgresMain()函数中根据输入的查询语句"select * from shoelace"读取首字符firstchar,并将其分为"Q"(simple query),然后执行exec_simple_query()函数

static void exec_simple_query(const char *query_string){
    
    
    /*
        该该函数用于执行”simple query“协议信息
        在该函数中调用函数pg_parse_query进行词法解析并产生解析树,返回给变量parsetree_list
        然后调用pg_analyze_and_rewrite函数逐个对paarsetree_list中的元素进行语义分析和重写
        参数是查询语句的地址
    */
}

查询的执行流程为:

  1. 经过词法分析将SQL命令转成解析树
  2. 语义分析将解析树转成查询树
  3. 为查询树生成plan

1.1 exec_simple_query中调用pg_parse_query

词法分析函数

List* pg_parse_query(const char* query_string){
    
    
    /*
        该函数处理SQL命令生成分析树的List

        返回值类型是List的指针类型
    */
}

// List数据结构
typedef struct List
{
    
    
	NodeTag		type;			/* T_List, T_IntList, or T_OidList */
	int			length;			/* number of elements currently present */
	int			max_length;		/* allocated length of elements[] */
	ListCell   *elements;		/* re-allocatable array of cells */
	/* We may allocate some cells along with the List header: */
	ListCell	initial_elements[FLEXIBLE_ARRAY_MEMBER];
	/* If elements == initial_elements, it's not a separate allocation */
} List;

1.1.1 pg_parse_query中调用raw_parser

在pg_parse_query函数中中调用raw_parser生成分析树,并将返回值给变量raw_parse_tree_list

List* raw_parser(const char* str, RowParseMode mode){
    
    
    /*
        给定字符串形式的查询,进行词法和语法分析。
        返回原始(未分析)解析树列表。 该列表的内容具有指定的 RawParseMode 所要求的形式。

        在简单查询中,mode为RAW_PARSE_DEFAULT
    */
}

不理解该函数的执行过程,它返回一个List* 类型的指针
对于该SQL它打印出来为

(gdb) print *raw_parsetree_list
{
    
    type = T_List, length = 1, max_length = 5, elements = 0x157e070, initial_elements = 0x157e070}
(gdb) p*(Node *)(raw_parsetree_list->elements->ptr_value)
$9 = {
    
    type = T_RawStmt}
(gdb) p*(RawStmt *)(parsetree_list->elements->ptr_value)
No symbol "parsetree_list" in current context.
(gdb) p*(RawStmt *)(raw_parsetree_list->elements->ptr_value)
$10 = {
    
    type = T_RawStmt, stmt = 0x157df18, stmt_location = 0, stmt_len = 22}
(gdb) p	*(((RawStmt *)(raw_parsetree_list->elements->ptr_value))->stmt)
$11 = {
    
    type = T_SelectStmt}

数据结构

typedef struct SelectStmt
{
    
    
	NodeTag		type;

	/*
	 * 这些字段仅在"叶子" SelectStmt 中使用。
	 */
	List	   *distinctClause; /* NULL,DISTINCT ON 表达式列表,或者
								 * lcons(NIL,NIL) 表示所有 (SELECT DISTINCT) */
	IntoClause *intoClause;		/* SELECT INTO 的目标 */
	List	   *targetList;		/* 目标列表(ResTarget 列表) */
	List	   *fromClause;		/* FROM 子句 */
	Node	   *whereClause;	/* WHERE 条件 */
	List	   *groupClause;	/* GROUP BY 子句 */
	bool		groupDistinct;	/* 是否为 GROUP BY DISTINCT? */
	Node	   *havingClause;	/* HAVING 条件表达式 */
	List	   *windowClause;	/* WINDOW window_name AS (...), ... */

	/*
	 * 在表示 VALUES 列表的"叶子"节点中,上述字段都为空,取而代之的是这个字段。
	 * 需要注意的是,子列表的元素仅为表达式,没有 ResTarget 的修饰。
	 * 还需要注意,列表元素可以是 DEFAULT(表示为 SetToDefault 节点),
	 * 无论 VALUES 列表的上下文如何。在解析分析中,需要在不合法的情况下拒绝这种情况。
	 */
	List	   *valuesLists;	/* 未转换的表达式列表 */

	/*
	 * 这些字段在"叶子" SelectStmt 和上层 SelectStmt 中都使用。
	 */
	List	   *sortClause;		/* 排序子句(SortBy 列表) */
	Node	   *limitOffset;	/* 跳过的结果元组数目 */
	Node	   *limitCount;		/* 返回的结果元组数目 */
	LimitOption limitOption;	/* 限制类型 */
	List	   *lockingClause;	/* FOR UPDATE(LockingClause 列表) */
	WithClause *withClause;		/* WITH 子句 */

	/*
	 * 这些字段仅在上层 SelectStmt 中使用。
	 */
	SetOperation op;			/* 集合操作的类型 */
	bool		all;			/* 是否指定了 ALL? */
	struct SelectStmt *larg;	/* 左子树 */
	struct SelectStmt *rarg;	/* 右子树 */
	/* 最终在这里添加对应规范的字段 */
} SelectStmt;

SQL语句经过raw_parser函数解析后,存储在raw_parsetree_list中,其中List存储SelectStmt结构体,数据结构为链表的形式。每一条SQL语句被解析后存储在结构体SelectStmt中

当词法解析完成后,将变量raw_parsetree_list返回给pg_parse_query函数中的变量parsetree_list

1.2 exec_simple_query中调用pg_analyze_and_rewrite_fixedparams

然后再对得到的变量paarsetree_list中的元素逐个进行语义分析和重写

使用的函数是pg_analyze_and_rewrite_fixedparams

List* pg_analyze_and_rewrite_fixedparams(RawStmt *parsetree,
                                         const char *query_string,
                                         const Oid *paramTypes,
                                         int numParams,
                                         QueryEnvironment *queryEnv){
    
    
    /*
        对原始解析树和执行分析和重写
        
        由于分析器或重写器可能会将一个查询扩展为多个查询,因此会返回一个查询节点列表。
    */
}

1.2.1 pg_analyze_and_rewrite_fixedparams中调用parse_analyze_fixedparams

执行分析的函数为parse_analyze_fixedparams

Query *parse_analyze_fixedparams(RawStmt *parseTree, const char *sourceText,
						  const Oid *paramTypes, int numParams,
						  QueryEnvironment *queryEnv){
    
    
    /*
        分析原始解析树并将其转换为查询形式。
        返回值为Query节点指针类型
    */
}

Query结构体:

typedef struct Query
{
    
    
	NodeTag		type;

	CmdType		commandType;	/* select|insert|update|delete|merge|utility */

	QuerySource querySource;	/* 查询来源 */

	uint64		queryId;		/* 查询标识符(可以由插件设置) */

	bool		canSetTag;		/* 是否设置命令结果标记? */

	Node	   *utilityStmt;	/* 若 commandType == CMD_UTILITY,则为非空 */

	int			resultRelation; /* INSERT/UPDATE/DELETE/MERGE 的目标关系的 rtable 索引;SELECT 为 0 */

	bool		hasAggs;		/* 目标列表或 havingQual 中是否有聚合函数 */
	bool		hasWindowFuncs; /* 目标列表中是否有窗口函数 */
	bool		hasTargetSRFs;	/* 目标列表中是否有返回集函数 */
	bool		hasSubLinks;	/* 是否有子查询 SubLink */
	bool		hasDistinctOn;	/* distinctClause 是否来自 DISTINCT ON */
	bool		hasRecursive;	/* 是否指定了 WITH RECURSIVE */
	bool		hasModifyingCTE;	/* WITH 中是否有 INSERT/UPDATE/DELETE */
	bool		hasForUpdate;	/* 是否指定了 FOR [KEY] UPDATE/SHARE */
	bool		hasRowSecurity; /* 重写器是否应用了某些 RLS 策略 */

	bool		isReturn;		/* 是否为 RETURN 语句 */

	List	   *cteList;		/* WITH 列表(CommonTableExpr 列表) */

	List	   *rtable;			/* 范围表条目列表 */

	FromExpr   *jointree;		/* 表联接树(FROM 和 WHERE 子句);MERGE 中的 USING 子句 */

	List	   *mergeActionList;	/* MERGE 的操作列表 */
	bool		mergeUseOuterJoin;	/* 是否使用外连接 */

	List	   *targetList;		/* 目标列表(TargetEntry 列表) */

	OverridingKind override;	/* OVERRIDING 子句 */

	OnConflictExpr *onConflict; /* ON CONFLICT DO [NOTHING | UPDATE] */

	List	   *returningList;	/* 返回值列表(TargetEntry 列表) */

	List	   *groupClause;	/* 排序分组子句(SortGroupClause 列表) */
	bool		groupDistinct;	/* 分组是否使用 DISTINCT? */

	List	   *groupingSets;	/* 若存在,表示 GroupingSet 列表 */

	Node	   *havingQual;		/* 应用于分组的限制条件 */

	List	   *windowClause;	/* 窗口子句列表(WindowClause 列表) */

	List	   *distinctClause; /* DISTINCT 子句列表(SortGroupClause 列表) */

	List	   *sortClause;		/* 排序子句列表(SortGroupClause 列表) */

	Node	   *limitOffset;	/* 跳过的结果元组数目(int8 表达式) */
	Node	   *limitCount;		/* 返回的结果元组数目(int8 表达式) */
	LimitOption limitOption;	/* 限制类型 */

	List	   *rowMarks;		/* RowMarkClause 列表 */

	Node	   *setOperations;	/* 若是 UNION/INTERSECT/EXCEPT 查询的顶层,则为集合操作树 */

	List	   *constraintDeps; /* 语句依赖的 pg_constraint OID 列表 */

	List	   *withCheckOptions;	/* WithCheckOption 列表(在重写期间添加) */

	/*
	 * 下面两个字段标识包含此查询的源文本字符串的部分。通常只在顶层查询中填充,
	 * 而不是在子查询中填充。当未设置时,它们可能都为零,或者都为 -1,表示"未知"。
	 */
	int			stmt_location;	/* 起始位置,若未知则为 -1 */
	int			stmt_len;		/* 字节长度;0 表示"剩余部分" */
} Query;

在函数parse_analyze_fixedparams中调用transformTopLevelStmt函数将parse tree 转换成 query tree
通过transformStmt函数选择合适的处理模式

在该语句中使用的是transformSelectStmt函数来执行

transformSelectStmt函数中根据结构体SelectStmt对Query结构体进行赋值

最终将Query类型的结构体返回给pg_analyze_and_rewrite_fixedparams中的参数变量query,完成parse tree 到 query tree的转换

1.2.2 pg_analyze_and_rewrite_fixedparams中调用pg_rewrite_query

pg_analyze_and_rewrite_fixedparams函数还需要完成对query的重写
调用pg_rewrite_query函数,将值返回给变量querytree_list


List* pg_rewrite_query(Query* query){
    
    
    /*
        对解析分析产生的查询执行重写。

        参数是parse_analyze_fixedparams函数的返回值
        返回值是List类型的指针
    */
}

在该函数中调用QueryRewrite函数做重写常规查询,QueryRewrite的参数是pg_rewrite_query的形参,返回值又作为pg_rewrite_query的返回值返回给pg_analyze_and_rewrite_fixedparams函数,最终返回给exec_simple_query函数中的变量querytree_list

List* QueryRewrite(Query* parsetree){
    
    
    /*
        通过该函数重写一个查询,可能返回 0 个或多个查询。
        参数:解析树,是pg_rewrite_query的形参
        返回值:List类型的指针
    */
}

主要步骤:

step 1 : 应用所有非 SELECT 规则,可能得到 0 个或多个查询
调用RewriteQuery函数

static List* RewriteQuery(Query* parsetree, List* rewrite_events, int orig_rt_length){
    
    
    /*
        应用所有非 SELECT 规则,可能得到 0 个或多个查询
        参数: parsetree:分析树,QueryRewrite的传参
              rewrit_events: 空的List
              orig_rt_length : 0
        返回值:rewritten : List类型的指针,是重写后的查询
    */
}

对于SELECT查询语句,在该函数中调用lappend函数,返回值是rewritten

List* lappend(List* list, void* datum){
    
    
    /*
        向列表添加一个指针。返回指向修改后列表的指针。
    */
}

最终lappend的返回值传递给RewriteQuery的返回值,作为QueryRewrite函数中的变量querylist。

step 2 : 对每个查询应用所有 RIR 规则
遍历querylist,对每个query都执行RIR规则(在该查询语句中querylist只有一个query)
通过调用firRIRrules函数来实现RIR规则

static Query* fireRIRrules(Query* parsetree, List* activateRIRs){
    
    
    /*
        对parsetree应用RIR规则

        参数:parsetree : RewriteQuery返回List中的元素
            activateRIRs:已经使用RIR处理后的views的OID列表,这里默认为空List
    */
}

fireRIRrules调用rewriteRuleAction函数将RIR规则应用到查询树中,得到一个查询树的链表返回给函数RewriteQuery,RewriteQuery的带重写后的查询列表之后还会对每一个查询树进行重写,最后返回给QueryRewrite函数的变量querylist

step 3 : 将step 2 得到的变量作为查询结果返回

最终将该结果返回给exec_simple_query函数的变量querytree_list

1.3 exec_simple_query中调用pg_plan_queries

在exec_simple_query函数中,执行完查询重写后,执行函数pg_plan_queries为重写后的querytree_list生成plan

List* pg_plan_queries(List* querytrees, const char *query_string, int cursorOptions, ParamListInfo boundParams){
    
    
    /*
        为重写后的querytree_list生成plan
        参数: querytrees : 重写后的解析树
              query_string : 输入的命令
              cursorOptions : 这里为CURSOR_OPT_PARALLEL_OK 0x0800 并行
              boundParams : NULL
        返回值 : 生成的plan列表 为List类型的指针
    */
}

函数pg_plan_queries是为所有query递归生成plans
在其内部使用pg_plan_query为每个query生成plan
最后在pg_plan_queries中将plan添加到List中

PlannedStmt* pg_plan_query(Query *querytree, const char *query_string, int cursorOptions,
			  ParamListInfo boundParams){
    
    
    /*
        为一个已经重写的查询生成一个计划。
        参数: querytrees : 重写后的解析树
            query_string : 输入的命令
            cursorOptions : 这里为CURSOR_OPT_PARALLEL_OK 0x0800 并行
            boundParams : NULL
        返回值 : 生成的plan 是PlannedStmt类型的指针,为List中的元素
    */
}

在pg_plan_query函数中调用planner再调用standard_planner函数生成具体的plan

PlannedStmt* standard_planner(Query *parse, const char *query_string, int cursorOptions,
				 ParamListInfo boundParams){
    
    
    /*
        为一个已经重写的查询生成一个计划。
        参数: querytrees : 重写后的解析树
            query_string : 输入的命令
            cursorOptions : 这里为CURSOR_OPT_PARALLEL_OK 0x0800 并行
            boundParams : NULL
        返回值 : 生成的plan 是PlannedStmt类型的指针,为List中的元素
    */
}

最终返回值返回给pg_plan_queries函数中的stmt变量
并在pg_plan_queries函数中添加到List中,返回给exec_simple_query函数中的变量plantree_list

1.4 执行阶段 exec_simple_query中调用PortalRun

查询执行一个sQL语句时,用一个Portal作为输入数据,它包含着SQL语句相关的的所有信息:如查询树、计划树、执行状态等。Portal结构体如下

typedef struct PortalData
{
    
    
    /* 书面数据 */
    const char *name;           /* Portal的名称 */
    const char *prepStmtName;   /* 源预处理语句(如果没有则为NULL) */
    MemoryContext portalContext;    /* Portal的辅助内存 */
    ResourceOwner resowner;     /* Portal拥有的资源 */
    void        (*cleanup) (Portal portal); /* 清理hook */

    /*
     * 记录门户被创建或使用的子事务状态数据。如果门户是从前一个事务中保留下来的,
     * 则两个 subxids 都是 InvalidSubTransactionId。否则,createSubid 是创建子事务,
     * activeSubid 是我们运行门户的最后一个子事务。
     */
    SubTransactionId createSubid;   /* 创建子事务 */
    SubTransactionId activeSubid;   /* 最后一个有活动的子事务 */
    int         createLevel;    /* 创建子事务的嵌套层级 */

    /* 门户将执行的查询或查询 */
    const char *sourceText;     /* SQL语句(从8.4开始,永不为NULL) */
    CommandTag  commandTag;     /* 原始查询的命令标记 */
    QueryCompletion qc;         /* 执行查询的命令完成数据 */
    List       *stmts;          /* PlannedStmts 列表 */
    CachedPlan *cplan;          /* CachedPlan,如果 stmts 来自其中之一 */

    ParamListInfo portalParams; /* 传递给查询的参数 */
    QueryEnvironment *queryEnv; /* 查询环境 */

    /* 特性/选项 */
    PortalStrategy strategy;    /* 见上面 */
    int         cursorOptions;  /* DECLARE CURSOR 选项位 */
    bool        run_once;       /* Portal只会运行一次 */

    /* 状态数据 */
    PortalStatus status;        /* 见上面 */
    bool        portalPinned;   /* 固定的Portal无法被删除 */
    bool        autoHeld;       /* 自动从固定转换为持有(参见 HoldPinnedPortals()) */

    /* 如果不为 NULL,则 Executor 正在活动中;最终需要调用 ExecutorEnd: */
    QueryDesc  *queryDesc;      /* 执行器调用所需的信息 */

    /* 如果门户返回元组,则这是它们的 tupdesc: */
    TupleDesc   tupDesc;        /* 结果元组的描述符 */
    /* 这些是用于列的格式代码: */
    int16      *formats;        /* 每列的格式代码 */

    /*
     * 用于门户查询执行的最外层 ActiveSnapshot。除了一些实用程序命令外,
     * 我们需要这样一个快照来存在。这确保查询结果中的 TOAST 引用可以被解压,
     * 并有助于减少进程的暴露的 xmin。
     */
    Snapshot    portalSnapshot; /* 活跃快照,如果没有则为 NULL */

    /*
     * 为持有的游标或 PORTAL_ONE_RETURNING 或 PORTAL_UTIL_SELECT 查询存储元组的位置。
     * (超过其事务结束的游标不再有任何活动的执行状态。)
     */
    Tuplestorestate *holdStore; /* 用于可持有游标的存储 */
    MemoryContext holdContext;  /* 包含 holdStore 的内存 */

    /*
     * 用于从 holdStore 读取元组的快照。如果元组可能包含 TOAST 引用,我们必须
     * 保持对此快照的引用,因为释放快照可能允许最近死亡的行被清理,
     * 以及属于它们的任何 TOAST 数据。对于一个持有的游标,我们避免需要
     * 保持这样一个快照,通过强制解压数据。
     */
    Snapshot    holdSnapshot;   /* 注册的快照,如果没有则为 NULL */

    /*
     * atStart、atEnd 和 portalPos 表示当前游标位置。在第一行之前,portalPos 为零,
     * 在获取查询的第 N 行后,portalPos 为 N。在我们执行结束之后,
     * portalPos = 查询中的行数,atEnd 为 true。注意,atStart 意味着 portalPos == 0,
     * 但反之不成立:我们可能只回退到第一行,而不是到开头。另请注意,
     * 各种代码检查 atStart 和 atEnd,但只有门户移动例程应该触摸 portalPos。
     */
    bool        atStart;
    bool        atEnd;
    uint64      portalPos;

    /* 演示数据,主要用于 pg_cursors 系统视图 */
    TimestampTz creation_time;   /* 定义此Portal的时间 */
    bool        visible;        /* 是否包括此Portal在 pg_cursors 中? */
}           PortalData;

执行过程

  1. PortalStart:初始化
  2. PortalRun:执行
  3. PortalDrop:清理

主要跟踪执行过程中的函数
在exec_simply_query中通过调用PortalRun来执行语句

PortalRun函数运行一个或多个Port查询

bool PortalRun(Portal portal, long count, bool isTopLevel, bool run_once,
		  DestReceiver *dest, DestReceiver *altdest,
		  QueryCompletion *qc){
    
    
    /*
        函数运行一个或多个Port查询
        参数:
            portal:要执行的数据结构
            count : <= 0 解释为无操作:执行启动或者关闭,不会发生额外的操作
                    FETCH_ALL, 解释为对查询所有记录,是该语句所传递的参数
            isTopLevel:如果查询是在后台 "顶层 "执行,则为 true。传入为true
            run_once:是否一次运行,传入为true
            dest:主查询发送的位置
            altdest: 非主查询发送位置
            qc:存储命令完成状态数据的位置
        返回 :完成执行返回true,否则false
    */
}

在PortalRun函数中,根据portal->strategy == PORTAL_ONE_SELECT执行查询及换,对于本语句调用函数PortalRunSelect

1.4.1 执行查询计划 ExecutePlan函数

查询计划的实际执行由函数execute完成,
PortalRun调用PortalRunSelect,
PortalRunSelect函数调用ExecutorRun,
ExecutorRun再次调用standard_ExecutorRun,
standard_ExecutorRun最后调用ExecutePlan完成

static void ExecutePlan(EState *estate,
			PlanState *planstate,
			bool use_parallel_mode,
			CmdType operation,
			bool sendTuples,
			uint64 numberTuples,
			ScanDirection direction,
			DestReceiver *dest,
			bool execute_once)
{
    
    
    /*
        该函数是一个循环,每一次循环都通过ExecProcNode函数从计划节点树中获取一个元组,并对该进行相应的处理,然后返回处理结果。当ExecProcNode从计划节点状态数中取不到有效元组,结束循环
    */
}
static inline TupleTableSlot *ExecProcNode(PlanState *node){
    
    
    /*
        从node中获取元组
    */
}

1.5 结果输出 ReadyForQuery函数

该函数在PostgresMain中调用,查询执行完毕,告诉 dest 可以进行新的查询了。并将之前的查询结果输出到client上

在该函数中调用socket_flush函数,将查询结果刷到client中

socket_flush调用internal_flush 来 flush待处理的输出
函数中利用send来发送数据。

猜你喜欢

转载自blog.csdn.net/weixin_47895938/article/details/132314630
今日推荐