美文网首页
查询模块源码解读

查询模块源码解读

作者: 奥利奥蘸墨水 | 来源:发表于2022-05-27 17:17 被阅读0次

    这次要解读的查询语句如下:

    select * from classes;
    

    执行

    建表语句

    create table classes (
        id integer primary key not null,
        name varchar(30) not null
    );
    

    数据

    -- classes
    0, 'A'
    1, 'B'
    2, 'C'
    3, 'D'
    

    查询结果

     id | name
    ----+------
      0 |  'A'
      1 |  'B'
      2 |  'C'
      3 |  'D'
    (4 rows)
    

    跟源码

    主流程

    程序入口exec_simple_query,此处query_string就是我们输入的查询命令,MessageType为QUERY_MESSAGE。

    exec_simple_query (query_string=0x7f311b82c060 "select * from classes;", messageType=QUERY_MESSAGE, msg=0x7f3124c40a90)
    

    exec_simple_query函数的主流程如下:

    static void exec_simple_query(const char* query_string, MessageType messageType, StringInfo msg = NULL)
    {
        ...
        // 报告后端线程正在处理查询语句
        pgstat_report_activity(STATE_RUNNING, query_string);
        ...
        // 开启事务
        start_xact_command();
        ...
        // SQL解析
        parsetree_list = pg_parse_query(reparsed_query.empty() ?
                                                    query_string : reparsed_query.c_str(), &query_string_locationlist);
        ...
        /*
         * Run through the raw parsetree(s) and process each one.
         */
        // 遍历parsetree_list
        foreach (parsetree_item, parsetree_list) {
            ...
            Node* parsetree = (Node*)lfirst(parsetree_item);
            ...
            // 操作类型,当前为"SELECT"
            commandTag = CreateCommandTag(parsetree);
            ... 
            /* Make sure we are in a transaction command */
            start_xact_command();
            ...
            /*
             * Set up a snapshot if parse analysis/planning will need one.
             */
            // 设置快照
            if (analyze_requires_snapshot(parsetree)) {
                PushActiveSnapshot(GetTransactionSnapshot());
                snapshot_set = true;
            }
            ...
            // 分析解析树转换为查询树并重写查询树
            querytree_list = pg_analyze_and_rewrite(parsetree, query_string, NULL, 0);
            ...
            // 生成计划树
            plantree_list = pg_plan_queries(querytree_list, 0, NULL);
            ...
            // 创建未命令的portal来运行查询
            portal = CreatePortal("", true, true);
            ...
            // 启动portal
            PortalStart(portal, NULL, 0, InvalidSnapshot);
            ...
            // 运行portal,然后删除它及receiver
            (void)PortalRun(portal, FETCH_ALL, isTopLevel, receiver, receiver, completionTag);
            (*receiver->rDestroy)(receiver);
            PortalDrop(portal, false);
            ...
            // 事务提交
            finish_xact_command();
            ...
            // 命令完成
            EndCommand(completionTag, dest);
            ...
        }
    }
    

    查询编译

    除开事务部分,这个函数又可以分为查询编译部分和查询执行部分。查询编译部分的两个主要函数如下:

    parsetree_list = pg_parse_query(); // 词法分析和语法分析
    querytree_list = pg_analyze_and_rewrite(parsetree, query_string, NULL, 0); // 语义分析和重写
    

    pg_parse_query()会在内部传递sql_query_string给raw_parse()函数,由raw_parse()函数调用base__yyparse进行词法分析和语法分析,生成语法树添加到链表parsetree_list中。生成的分析树会传给pg_analyze_and_rewrite()函数进行语义分析和重写,其中pg_analyze_and_rewrite()会调用parse_analyze()函数进行语义分析并生成查询树,用query结构体表示。之后会将查询树传递给函数pg_write_query()进行查询重写。

    在上述的此法分析和语法分析阶段。最终会生成一个List结构,包含了所有的语法分析树,该lList中的每个ListCell包含一棵语法分析树。这个结构将会用于后续的语义分析。

    语义分析的关键结构是ParseState,其保存了很多语义分析的中间信息。结构如下:

    struct ParseState {
        struct ParseState* parentParseState; /* 指向外层查询 */
        const char* p_sourcetext;            /* 原始sql命令 */
        List* p_rtable;                      /* 范围表 */
        List* p_joinexprs;                   /* 连接表达式 */
        List* p_joinlist;                    /* 连接项 */
        List* p_relnamespace;                /* 表名集合 */
        List* p_varnamespace;                /* 属性名集合 */
        bool  p_lateral_active;              
        List* p_ctenamespace;                /* 公共表达式集合 */
        List* p_future_ctes;                 /* 不在p_ctenamespace中的公共表达式 */
        CommonTableExpr* p_parent_cte;       
        List* p_windowdefs;                  /* window子句的原始定义 */
        ParseExprKind p_expr_kind;           
        List* p_rawdefaultlist;              
        int p_next_resno;                    /* 下一个分配给目标属性的资源号 */
        List* p_locking_clause;              /* 原始的for update/for share信息 */
        Node* p_value_substitute;            
        bool p_hasAggs;                      /* 是否有聚集函数 */
        bool p_hasWindowFuncs;               /* 是否有窗口函数 */
        bool p_hasSubLinks;                  /* 是否有自链接 */
        bool p_hasModifyingCTE;
        bool p_is_insert;                    /* 是否为insert语句 */
        bool p_locked_from_parent;
        bool p_resolve_unknowns; /* resolve unknown-type SELECT outputs as type text */
        bool p_hasSynonyms;
        Relation p_target_relation;
        RangeTblEntry* p_target_rangetblentry; // 目标表在range_table对应的项
        ...
    

    在parse_analyze函数中生成ParseState结构,然后传递到transformTopLevelStmt()函数中,transformTopLevelStmt会继续传递其到transformStmt()函数中进行赋值。在transformStmt()函数中,会判断parseTree的类型,然后传递给各个专有的transform函数进行赋值。这里的select语句就会进入这个分支。

    case T_SelectStmt: {
        SelectStmt* n = (SelectStmt*)parseTree;
        if (n->valuesLists) {
            result = transformValuesClause(pstate, n);
        } else if (n->op == SETOP_NONE) {
            if (analyzerRoutineHook == NULL || analyzerRoutineHook->transSelect == NULL) {
                result = transformSelectStmt(pstate, n, isFirstNode, isCreateView);
            } else {
                result = analyzerRoutineHook->transSelect(pstate, n, isFirstNode, isCreateView);
            }
        } else {
            result = transformSetOperationStmt(pstate, n);
        }
    };
    

    这里select有三种处理函数:

    transformValuesClause();         // 处理带有select value的语句的语义
    transformSelectStmt();             // 处理基本的select语句的语义
    transformSetOperationStmt(); // 处理带有union、intersect、except的select语句的语义
    

    这里因为执行的sql语句没有values,所以会进入transformSelectStmt进行处理。

    transformSelectStmt (pstate=0x7efc25f94060, stmt=0x7efc26a7d7f8, isFirstNode=true, isCreateView=false) at analyze.cpp:2165
    

    主要流程如下:


    这样查询的结果会保存在Query结构体中,然后返回给exec_simple_query函数。
    Query结构体如下所示:

    typedef struct Query {
        NodeTag type;
    
        CmdType commandType; /* 命令类型 */
    
        QuerySource querySource; /* 查询来源 */
    
        uint64 queryId; /* 查询树的识别符 */
    
        bool canSetTag; /* 如果是原始查询则为false,如果是查询重写或者是查询规划新增则为true */
    
        Node* utilityStmt; /* 定义游标或者不可优化的查询语句 */
    
        int resultRelation; /* 结果关系 */
    
        bool hasAggs;         /* 目标属性或者having子句是否有聚集函数 */
        bool hasWindowFuncs;  /* 目标属性中是否有窗口函数 */
        bool hasSubLinks;     /* 是否有子查询 */
        bool hasDistinctOn;   /* 是否有distinct子句 */
        bool hasRecursive;    /* 公共表达式是否允许递归 */
        bool hasModifyingCTE; /* with 子句是否包含insert/update/delete */
        bool hasForUpdate;    /* 是否有for update或者for share子句 */
        bool hasRowSecurity;  /* 重写是否应用行级访问控制 */
        bool hasSynonyms;     /* 范围表是否有同义词 */
    
        List* cteList; /* with子句,用于公共表达式 */
    
        List* rtable;       /* 范围表 */
        FromExpr* jointree; /* 连接树,描述from和where子句出现的连接 */
    
        List* targetList; /* 目标属性 */
    
        List* starStart; /* Corresponding p_star_start in ParseState */
    
        List* starEnd; /* Corresponding p_star_end in ParseState */
    
        List* starOnly; /* Corresponding p_star_only in ParseState */
    
        List* returningList; /* return-values list (of TargetEntry) */
    
        List* groupClause; /* a list of SortGroupClause's */
    
        List* groupingSets; /* a list of GroupingSet's if present */
    
        Node* havingQual; /* qualifications applied to groups */
    
        List* windowClause; /* a list of WindowClause's */
    
        List* distinctClause; /* a list of SortGroupClause's */
    
        List* sortClause; /* a list of SortGroupClause's */
    
        Node* limitOffset; /* # of result tuples to skip (int8 expr) */
        Node* limitCount;  /* # of result tuples to return (int8 expr) */
    
        List* rowMarks; /* a list of RowMarkClause's */
    
        Node* setOperations; /* set-operation tree if this is top level of
                              * a UNION/INTERSECT/EXCEPT query */
    
        List *constraintDeps; /* a list of pg_constraint OIDs that the query
                               * depends on to be semantically valid */
        HintState* hintState;
        ...
    

    Query会传递给优化器产生进行重写优化。

    List* QueryRewrite(Query* parsetree)
    {
        ...
        /*
         * Step 1
         *
         * Apply all non-SELECT rules possibly getting 0 or many queries
         */
        // 应用所有non-SELECT重写
        querylist = RewriteQuery(parsetree, NIL);
    
        /*
         * Step 2
         *
         * Apply all the RIR rules on each query
         *
         * This is also a handy place to mark each query with the original queryId
         */
        // 应用所有RIR规则
        results = NIL;
        foreach (l, querylist) {
            Query* query = (Query*)lfirst(l);
    
            query = fireRIRrules(query, NIL, false);
    
            query->queryId = input_query_id;
    
            results = lappend(results, query);
        }
        ...
        return results;
    }
    

    计划树的结构如下:

    typedef struct Plan {
        NodeTag type;
    
        int plan_node_id;   /* node id */
        int parent_node_id; /* parent node id */
        RemoteQueryExecType exec_type;
    
        /*
         * estimated execution costs for plan (see costsize.c for more info)
         */
        Cost startup_cost; /* cost expended before fetching any tuples */
        Cost total_cost;   /* total cost (assuming all tuples fetched) */
    
        /*
         * planner's estimate of result size of this plan step
         */
        double plan_rows; /* number of global rows plan is expected to emit */
        double multiple;
        int plan_width; /* average row width in bytes */
        int dop;        /* degree of parallelism of current plan */
    
        /*
         * machine learning model estimations
         */
        double pred_rows;
        double pred_startup_time;
        double pred_total_time;
        long pred_max_memory;
        ...
    

    查询执行

    执行器总体执行流程包括策略选择模块,Portal、执行组件executor和ProcessUtility。
    Portal是执行SQL语句的载体,每条sql语句对应唯一的portal,不同的查询类型对应的portal类型也有区别。

    回到exec_simple_query函数,已经生成了计划树plantree_list。
    先调用CreatePortal创建Portal。

    portal = CreatePortal("", true, true);
    /* Don't display the portal in pg_cursors */
    portal->visible = false;
    

    Portal执行流程。


    Portal执行流程

    其中左边的分支为执行非DDL语句,右边的分支为执行DDL语句。

    相关文章

      网友评论

          本文标题:查询模块源码解读

          本文链接:https://www.haomeiwen.com/subject/hvuuprtx.html