查询模块源码解读

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

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语句。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容