语句的执行流程

语句的执行流程

  • openGauss进程的主函数main.cpp
/*
 * Any Postgres server process begins execution here.
 */
int main(int argc, char* argv[])
{
    char* mmap_env = NULL;
    syscall_lock_init();

    // 获取环境变量
    mmap_env = gs_getenv_r("GAUSS_MMAP_THRESHOLD");
    if (mmap_env != NULL) {
        check_backend_env(mmap_env);
        mmap_threshold = (size_t)atol(mmap_env);
    }

    // 实例上下文初始化
    knl_instance_init();

    g_instance.increCheckPoint_context = AllocSetContextCreate(
        INSTANCE_GET_MEM_CXT_GROUP(MEMORY_CONTEXT_STORAGE),
        "IncreCheckPointContext",
        ALLOCSET_DEFAULT_MINSIZE,
        ALLOCSET_DEFAULT_INITSIZE,
        ALLOCSET_DEFAULT_MAXSIZE,
        SHARED_CONTEXT);

    g_instance.account_context = AllocSetContextCreate(g_instance.instance_context,
        "StandbyAccontContext",
        ALLOCSET_DEFAULT_MINSIZE,
        ALLOCSET_DEFAULT_INITSIZE,
        ALLOCSET_DEFAULT_MAXSIZE,
        SHARED_CONTEXT);

    /*
     * Fire up essential subsystems: error and memory management
     *
     * Code after this point is allowed to use elog/ereport, though
     * localization of messages may not work right away, and messages won't go
     * anywhere but stderr until GUC settings get loaded.
     */
    // 启动内存上下文子系统
    MemoryContextInit();

    PmTopMemoryContext = t_thrd.top_mem_cxt;

    // 初始化主线程
    knl_thread_init(MASTER_THREAD);

    t_thrd.fake_session = create_session_context(t_thrd.top_mem_cxt, 0);
    t_thrd.fake_session->status = KNL_SESS_FAKE;

    u_sess = t_thrd.fake_session;

    SelfMemoryContext = THREAD_GET_MEM_CXT_GROUP(MEMORY_CONTEXT_DEFAULT);

    MemoryContextSwitchTo(THREAD_GET_MEM_CXT_GROUP(MEMORY_CONTEXT_DEFAULT));

    progname = get_progname(argv[0]); // 获取程序名

    /*
     * Platform-specific startup hacks
     * 平台特有的启动设置
     */
    startup_hacks(progname);

    /* if gaussdb's name is gs_encrypt, so run in encrypte_main() */
    // 如果程序名是gs_encrypt,调用encrypte_main将加密串返回
    if (!strcmp(progname, "gs_encrypt")) {
        return encrypte_main(argc, argv);
    }

    init_plog_global_mem();

    /*
     * Remember the physical location of the initially given argv[] array for
     * possible use by ps display.  On some platforms, the argv[] storage must
     * be overwritten in order to set the process title for ps. In such cases
     * save_ps_display_args makes and returns a new copy of the argv[] array.
     *
     * save_ps_display_args may also move the environment strings to make
     * extra room. Therefore this should be done as early as possible during
     * startup, to avoid entanglements with code that might save a getenv()
     * result pointer.
     * 保存argc, argv
     */
    argv = save_ps_display_args(argc, argv);

    /*
     * If supported on the current platform, set up a handler to be called if
     * the backend/postmaster crashes with a fatal signal or exception.
     */
     // 平台相关,设置当后端进程/postmaster崩溃时可调用的处理程序
#if defined(WIN32) && defined(HAVE_MINIDUMP_TYPE) 
    pgwin32_install_crashdump_handler();
#endif

    /*
     * Set up locale information from environment.  Note that LC_CTYPE and
     * LC_COLLATE will be overridden later from pg_control if we are in an
     * already-initialized database.  We set them here so that they will be
     * available to fill pg_control during initdb.  LC_MESSAGES will get set
     * later during GUC option processing, but we set it here to allow startup
     * error messages to be localized.
     */
    // 从环境变量设置区域信息
    set_pglocale_pgservice(argv[0], PG_TEXTDOMAIN("gaussdb"));

#ifdef WIN32

    /*
     * Windows uses codepages rather than the environment, so we work around
     * that by querying the environment explicitly first for LC_COLLATE and
     * LC_CTYPE. We have to do this because initdb passes those values in the
     * environment. If there is nothing there we fall back on the codepage.
     */
    {
        char* env_locale = NULL;

        if ((env_locale = gs_getenv_r("LC_COLLATE")) != NULL) {
            check_backend_env(env_locale);
            pg_perm_setlocale(LC_COLLATE, env_locale);
        } else
            pg_perm_setlocale(LC_COLLATE, "");

        if ((env_locale = gs_getenv_r("LC_CTYPE")) != NULL) {
            check_backend_env(env_locale);
            pg_perm_setlocale(LC_CTYPE, env_locale);
        } else
            pg_perm_setlocale(LC_CTYPE, "");
    }
#else
    pg_perm_setlocale(LC_COLLATE, "");
    pg_perm_setlocale(LC_CTYPE, "");
#endif

    /*
     * We keep these set to "C" always, except transiently in pg_locale.c; see
     * that file for explanations.
     */
    pg_perm_setlocale(LC_MONETARY, "C");
    pg_perm_setlocale(LC_NUMERIC, "C");
    pg_perm_setlocale(LC_TIME, "C");

    /*
     * Now that we have absorbed as much as we wish to from the locale
     * environment, remove any LC_ALL setting, so that the environment
     * variables installed by pg_perm_setlocale have force.
     */
    (void)unsetenv("LC_ALL");

    /*
     * Catch standard options before doing much else
     */
    if (argc > 1) {
        // 打印帮助信息后退出
        if (strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-?") == 0) {
            help(progname);
            exit(0);
        }
        // 打印版本信息后退出
        if (strcmp(argv[1], "--version") == 0 || strcmp(argv[1], "-V") == 0) {
            puts("gaussdb " DEF_GS_VERSION);
            exit(0);
        }
    }

    /*
     * Make sure we are not running as root.
     */
    // 确认不是使用root启动的程序
    check_root(progname);

    /*
     * Dispatch to one of various subprograms depending on first argument.
     */
#ifdef WIN32

    /*
     * Start our win32 signal implementation
     *
     * SubPostmasterMain() will do this for itself, but the remaining modes
     * need it here
     */
    pgwin32_signal_initialize();
#endif

    /* init trace context */
    if (gstrace_init(getpid()) == 0) {
        on_proc_exit(gstrace_destory, 0);
    }

    t_thrd.mem_cxt.gs_signal_mem_cxt = AllocSetContextCreate(
        t_thrd.top_mem_cxt, "gs_signal", ALLOCSET_DEFAULT_MINSIZE, ALLOCSET_DEFAULT_INITSIZE, ALLOCSET_DEFAULT_MAXSIZE);
    if (NULL == t_thrd.mem_cxt.gs_signal_mem_cxt) {
        ereport(LOG, (errmsg("could not start a new thread, because of no  enough system resource. ")));
        proc_exit(1);
    }

    /*
     * @BuiltinFunc
     * Create a global BuiltinFunc object shared among threads
     */
    // 线程共享的全局内置函数对象
    if (g_sorted_funcs[0] == NULL) {
        initBuiltinFuncs();
    }

    if (argc > 1 && strcmp(argv[1], "--boot") == 0) {
        // initdb相关,bootstrapping模式
        IsInitdb = true;
        gs_signal_monitor_startup();
        gs_signal_slots_init(1);
        (void)gs_signal_unblock_sigusr2();
        gs_signal_startup_siginfo("AuxiliaryProcessMain");
        BootStrapProcessMain(argc, argv); /* does not return */
    }

    // 打印guc的变量
    if (argc > 1 && strcmp(argv[1], "--describe-config") == 0)
        exit(GucInfoMain());

    if (argc > 1 && strcmp(argv[1], "--single") == 0) {
        // initdb相关,单用户模式
        IsInitdb = true;
        gs_signal_monitor_startup();
        gs_signal_slots_init(1);
        (void)gs_signal_unblock_sigusr2();
        gs_signal_startup_siginfo("PostgresMain");

        exit(PostgresMain(argc, argv, NULL, get_current_username(progname)));
    }

    // 数据库启动
    exit(PostmasterMain(argc, argv));
}

查询语句的执行流程

postgres=# create table a(id int);
CREATE TABLE
postgres=# insert into a values(1);
INSERT
postgres=# select * from a;

PostgresMain.cpp ReadCommand函数读取客户端命令
简单查询调用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);
        ...
    }
}
  • 词法语法解析
    相关数据结构
typedef struct SelectStmt {
    NodeTag type;

    /*
     * These fields are used only in "leaf" SelectStmts.
     */
    List *distinctClause;   /* NULL, list of DISTINCT ON exprs, or
                             * lcons(NIL,NIL) for all (SELECT DISTINCT) */
    IntoClause *intoClause; /* target for SELECT INTO */
    List *targetList;       /* the target list (of ResTarget) */
    List *fromClause;       /* the FROM clause */
    Node *whereClause;      /* WHERE qualification */
    List *groupClause;      /* GROUP BY clauses */
    Node *havingClause;     /* HAVING conditional-expression */
    List *windowClause;     /* WINDOW window_name AS (...), ... */
    WithClause *withClause; /* WITH clause */

    /*
     * In a "leaf" node representing a VALUES list, the above fields are all
     * null, and instead this field is set.  Note that the elements of the
     * sublists are just expressions, without ResTarget decoration. Also note
     * that a list element can be DEFAULT (represented as a SetToDefault
     * node), regardless of the context of the VALUES list. It's up to parse
     * analysis to reject that where not valid.
     */
    List *valuesLists; /* untransformed list of expression lists */

    /*
     * These fields are used in both "leaf" SelectStmts and upper-level
     * SelectStmts.
     */
    List *sortClause;    /* sort clause (a list of SortBy's) */
    Node *limitOffset;   /* # of result tuples to skip */
    Node *limitCount;    /* # of result tuples to return */
    List *lockingClause; /* FOR UPDATE (list of LockingClause's) */
    HintState *hintState;

    /*
     * These fields are used only in upper-level SelectStmts.
     */
    SetOperation op;         /* type of set op */
    bool all;                /* ALL specified? */
    struct SelectStmt *larg; /* left child */
    struct SelectStmt *rarg; /* right child */

    /*
     * These fields are used by operator "(+)"
     */
    bool hasPlus;
    /* Eventually add fields for CORRESPONDING spec here */
} SelectStmt;

相关代码在parse.cpp,主要流程如下

// flex,bison进行语法解析
List* raw_parser(const char* str, List** query_string_locationlist)
{
    ...
    // 初始化词法分析器
    yyscanner = scanner_init(str, &yyextra.core_yy_extra, ScanKeywords, NumScanKeywords);
    ... 
    // 初始化语法分析器
    parser_init(&yyextra);

    // SQL解析
    yyresult = base_yyparse(yyscanner);

    /* Clean up (release memory) */
    scanner_finish(yyscanner);

    if (yyresult) { /* error */
        return NIL;
    }
    ...
    // 返回语法树
    return yyextra.parsetree;
}
  • 转换查询树并重写
    相关数据结构
typedef struct Query {
    NodeTag type;

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

    QuerySource querySource; /* where did I come from? */

    uint64 queryId; /* query identifier (can be set by plugins) */

    bool canSetTag; /* do I set the command result tag? */

    Node* utilityStmt; /* non-null if this is DECLARE CURSOR or a
                        * non-optimizable statement */

    int resultRelation; /* rtable index of target relation for
                         * INSERT/UPDATE/DELETE/MERGE; 0 for SELECT */

    bool hasAggs;         /* has aggregates in tlist or havingQual */
    bool hasWindowFuncs;  /* has window functions in tlist */
    bool hasSubLinks;     /* has subquery SubLink */
    bool hasDistinctOn;   /* distinctClause is from DISTINCT ON */
    bool hasRecursive;    /* WITH RECURSIVE was specified */
    bool hasModifyingCTE; /* has INSERT/UPDATE/DELETE in WITH */
    bool hasForUpdate;    /* FOR UPDATE or FOR SHARE was specified */
    bool hasRowSecurity;  /* rewriter has applied some RLS policy */
    bool hasSynonyms;     /* has synonym mapping in rtable */

    List* cteList; /* WITH list (of CommonTableExpr's) */

    List* rtable;       /* list of range table entries */
    FromExpr* jointree; /* table join tree (FROM and WHERE clauses) */

    List* targetList; /* target list (of TargetEntry) */

    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;
#ifdef PGXC
    /* need this info for PGXC Planner, may be temporary */
    char* sql_statement;                 /* original query */
    bool is_local;                       /* enforce query execution on local node
                                          * this is used by EXECUTE DIRECT especially. */
    bool has_to_save_cmd_id;             /* true if the query is such an INSERT SELECT
                                          * that inserts into a child by selecting
                                          * from its parent OR a WITH query that
                                          * updates a table in main query and inserts
                                          * a row to the same table in WITH query */
    bool vec_output;                     /* true if it's vec output. this flag is used in FQS planning  */
    TdTruncCastStatus tdTruncCastStatus; /* Auto truncation Cast added, only used for stmt in stored procedure or
                                            prepare stmt. */
    List* equalVars;                     /* vars appears in UPDATE/DELETE clause */
#endif
    ParamListInfo boundParamsQ;

    int mergeTarget_relation;
    List* mergeSourceTargetList;
    List* mergeActionList; /* list of actions for MERGE (only) */
    Query* upsertQuery;    /* insert query for INSERT ON DUPLICATE KEY UPDATE (only) */
    UpsertExpr* upsertClause; /* DUPLICATE KEY UPDATE [NOTHING | ...] */

    bool isRowTriggerShippable; /* true if all row triggers are shippable. */
    bool use_star_targets;      /* true if use * for targetlist. */

    bool is_from_full_join_rewrite; /* true if the query is created when doing
                                     * full join rewrite. If true, we should not
                                     * do some expression processing.
                                     * Please refer to subquery_planner.
                                     */
    uint64 uniqueSQLId;             /* used by unique sql id */
    bool can_push;
    bool        unique_check;               /* true if the subquery is generated by general
                                             * sublink pullup, and scalar output is needed */
    Oid* fixed_paramTypes; /* For plpy CTAS query. CTAS is a recursive call.CREATE query is the first rewrited.
                            * thd 2nd rewrited query is INSERT SELECT.whithout this attribute, DB will have
                            * an error that has no idea about $x when INSERT SELECT query is analyzed. */
    int fixed_numParams;
} Query;

相关代码在analyze.cpp,主要流程如下

List* pg_analyze_and_rewrite(Node* parsetree, const char* query_string, Oid* paramTypes, int numParams)
{
    ...
    // 分析语法树转换为查询树
    query = parse_analyze(parsetree, query_string, paramTypes, numParams);
    ...
    /*
     * (2) Rewrite the queries, as necessary
     */
    // 重写查询树
    querytree_list = pg_rewrite_query(query);
    ...
    // 返回查询树
    return querytree_list;
}
Query* parse_analyze(
    Node* parseTree, const char* sourceText, Oid* paramTypes, int numParams, bool isFirstNode, bool isCreateView)
{
     // 
     ParseState* pstate = make_parsestate(NULL);
     ...
     // 转换
     query = transformTopLevelStmt(pstate, parseTree, isFirstNode, isCreateView);
     ...
    pfree_ext(pstate->p_ref_hook_state);
    free_parsestate(pstate);
     ...
     // 返回查询树
     return query;
}
Query* transformTopLevelStmt(ParseState* pstate, Node* parseTree, bool isFirstNode, bool isCreateView)
{
    if (IsA(parseTree, SelectStmt)) {
        // 转换select...into语法为create table as语法
        SelectStmt* stmt = (SelectStmt*)parseTree;

        /* If it's a set-operation tree, drill down to leftmost SelectStmt */
        while (stmt != NULL && stmt->op != SETOP_NONE)
            stmt = stmt->larg;
        AssertEreport(stmt && IsA(stmt, SelectStmt) && stmt->larg == NULL, MOD_OPT, "failure to check parseTree");

        if (stmt->intoClause) {
            CreateTableAsStmt* ctas = makeNode(CreateTableAsStmt);

            ctas->query = parseTree;
            ctas->into = stmt->intoClause;
            ctas->relkind = OBJECT_TABLE;
            ctas->is_select_into = true;

            /*
             * Remove the intoClause from the SelectStmt.  This makes it safe
             * for transformSelectStmt to complain if it finds intoClause set
             * (implying that the INTO appeared in a disallowed place).
             */
            stmt->intoClause = NULL;

            parseTree = (Node*)ctas;
        }
    }
    // 转换查询树
    return transformStmt(pstate, parseTree, isFirstNode, isCreateView);
}
Query* transformStmt(ParseState* pstate, Node* parseTree, bool isFirstNode, bool isCreateView)
{
    ...
    switch (nodeTag(parseTree)) {
        ...
        case T_SelectStmt: {
            SelectStmt* n = (SelectStmt*)parseTree;
            ...
            // 分析select语法树
            result = transformSelectStmt(pstate, n, isFirstNode, isCreateView);
            ...
        } break;
        ...
    }
    ...
    // 返回查询树
    return result;
}
static Query* transformSelectStmt(ParseState* pstate, SelectStmt* stmt, bool isFirstNode, bool isCreateView)
{
    Query* qry = makeNode(Query);
    ...
    qry->commandType = CMD_SELECT; // 命令类型:select
    ...
    // 转换with子句
    if (stmt->withClause) {
        qry->hasRecursive = stmt->withClause->recursive;
        qry->cteList = transformWithClause(pstate, stmt->withClause);
        qry->hasModifyingCTE = pstate->p_hasModifyingCTE;
    }
    ...
    /* process the FROM clause */
    // 转换from子句
    transformFromClause(pstate, stmt->fromClause, isFirstNode, isCreateView);
    
    /* transform targetlist */
    // 将ResTarget的列表转换为TargetEntry的列表
    qry->targetList = transformTargetList(pstate, stmt->targetList);

    /* Transform operator "(+)" to outer join */
    // (+)语法为外连接
    if (stmt->hasPlus && stmt->whereClause != NULL) {
        transformOperatorPlus(pstate, &stmt->whereClause);
    }
    ...
    /* mark column origins */
    // 用源表的OID和列号标记Vars的目标列表列
    markTargetListOrigins(pstate, qry->targetList);

    /* transform WHERE
     * Only "(+)" is valid when  it's in WhereClause of Select, set the flag to be true
     * during transform Whereclause.
     */
    // 转换where子句
    setIgnorePlusFlag(pstate, true);
    qual = transformWhereClause(pstate, stmt->whereClause, "WHERE");
    setIgnorePlusFlag(pstate, false);

    /*
     * Initial processing of HAVING clause is just like WHERE clause.
     */
    // 转换having子句
    qry->havingQual = transformWhereClause(pstate, stmt->havingClause, "HAVING");

    /*
     * Transform sorting/grouping stuff.  Do ORDER BY first because both
     * transformGroupClause and transformDistinctClause need the results. Note
     * that these functions can also change the targetList, so it's passed to
     * them by reference.
     */
    // 转换order by子句
    qry->sortClause = transformSortClause(
        pstate, stmt->sortClause, &qry->targetList, true /* fix unknowns */, false /* allow SQL92 rules */);

    /*
     * Transform A_const to columnref type in group by clause, So that repeated group column
     * will deleted in function transformGroupClause. If not to delete repeated column, for
     * group by rollup can have error result, because we need set null to non- group column.
     *
     * select a, b, b
     *  from t1
     *  group by rollup(1, 2), 3;
     *
     * To this example, column b should not be set to null, but if not to delete repeated column
     * b will be set to null and two b value is not equal.
     */
    // 将group by子句的A_const转换为columnref类型
    if (include_groupingset((Node*)stmt->groupClause)) {
        transformGroupConstToColumn(pstate, (Node*)stmt->groupClause, qry->targetList);
    }

    // 转换group by子句
    qry->groupClause = transformGroupClause(pstate,
        stmt->groupClause,
        &qry->groupingSets,
        &qry->targetList,
        qry->sortClause,
        false /* allow SQL92 rules */);

    if (stmt->distinctClause == NIL) {
        qry->distinctClause = NIL;
        qry->hasDistinctOn = false;
    } else if (linitial(stmt->distinctClause) == NULL) {
        // 转换distinct子句
        /* We had SELECT DISTINCT */
        qry->distinctClause = transformDistinctClause(pstate, &qry->targetList, qry->sortClause, false);
        qry->hasDistinctOn = false;
    } else {
        // 转换distinct on子句
        /* We had SELECT DISTINCT ON */
        qry->distinctClause =
            transformDistinctOnClause(pstate, stmt->distinctClause, &qry->targetList, qry->sortClause);
        qry->hasDistinctOn = true;
    }

    /* transform LIMIT */
    // 转换limit子句
    qry->limitOffset = transformLimitClause(pstate, stmt->limitOffset, "OFFSET");
    qry->limitCount = transformLimitClause(pstate, stmt->limitCount, "LIMIT");

    /* transform window clauses after we have seen all window functions */
    // 窗口函数相关
    qry->windowClause = transformWindowDefinitions(pstate, pstate->p_windowdefs, &qry->targetList);

    /* resolve any still-unresolved output columns as being type text */
    // 将还未解析的输出列解析为类型文本
    if (pstate->p_resolve_unknowns) {
        resolveTargetListUnknowns(pstate, qry->targetList);
    }

    // 创建FromExpr节点
    qry->rtable = pstate->p_rtable;
    qry->jointree = makeFromExpr(pstate->p_joinlist, qual);

    qry->hasSubLinks = pstate->p_hasSubLinks;
    qry->hasWindowFuncs = pstate->p_hasWindowFuncs;
    // 检查各子句中是否存在不应该有的窗口函数
    if (pstate->p_hasWindowFuncs) {
        parseCheckWindowFuncs(pstate, qry);
    }
    qry->hasAggs = pstate->p_hasAggs;

    // 转换FOR UPDATE/SHARE子句
    foreach (l, stmt->lockingClause) {
        transformLockingClause(pstate, qry, (LockingClause*)lfirst(l), false);
    }

    qry->hintState = stmt->hintState;
    ...
    // 标记排序信息
    assign_query_collations(pstate, qry);

    /* this must be done after collations, for reliable comparison of exprs */
Check for aggregates where they shouldn't be and improper grouping.
    // 检查子句中不应该存在的聚集和不适当的分组
    if (pstate->p_hasAggs || qry->groupClause || qry->groupingSets || qry->havingQual) {
        parseCheckAggregates(pstate, qry);
    }
    // 返回查询树
    return qry;
}
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;
    /*
     * MPPDB Recursive-Union Support
     *
     * - @recursive_union_plan_nodeid
     *      Pointing to its belonging RecursiveUnion's plan node id to indate if we are
     *      under RecursiveUnion
     *
     * - @recursive_union_controller
     *      Indicate if current Plan node is controller node in recursive-union steps
     *
     * - @control_plan_nodeid
     *      Normally, set on the top-plan node of a producer thread, to indicate which
     *      control-plan we need syn-up with
     *
     * - @is_sync_planode
     *      Indicate the current producer thread is the sync-up thread in recursive union,
     *      normally set on produer's top plan node
     *
     * Please note the above four variables is meaningless if a plan node is not under
     * recursive-union's recursive part
     */
    /*
     * plan node id of RecursiveUnion node where current plan node belongs to, 0 for
     * not under recursive-union
     */
    int recursive_union_plan_nodeid;

    /* flag to indicate if it is controller plan node */
    bool recursive_union_controller;

    /* plan node id of Controller plan node, 0 for not in control */
    int control_plan_nodeid;

    /* flag indicate if the current plan node is the sync node (for multi-stream case) */
    bool is_sync_plannode;

    /*
     * Common structural data for all Plan types.
     */
    List* targetlist;      /* target list to be computed at this node */
    List* qual;            /* implicitly-ANDed qual conditions */
    struct Plan* lefttree; /* input plan tree(s) */
    struct Plan* righttree;

    bool ispwj;  /* is it special for partitionwisejoin? */
    int paramno; /* the partition'sn that it is scaning */

    List* initPlan;    /* Init Plan nodes (un-correlated expr
                        * subselects) */

    List* distributed_keys; /* distributed on which key */
    ExecNodes* exec_nodes;  /*  List of Datanodes where to execute this plan    */

    /*
     * Information for management of parameter-change-driven rescanning
     *
     * extParam includes the paramIDs of all external PARAM_EXEC params
     * affecting this plan node or its children.  setParam params from the
     * node's initPlans are not included, but their extParams are.
     *
     * allParam includes all the extParam paramIDs, plus the IDs of local
     * params that affect the node (i.e., the setParams of its initplans).
     * These are _all_ the PARAM_EXEC params that affect this node.
     */
    Bitmapset* extParam;
    Bitmapset* allParam;

    // For vectorized engine, plan produce vector output
    //
    bool vec_output;
    /*
     * @hdfs
     * Mark the foreign scan whether has unique results on one of its
     * output columns.
     */
    bool hasUniqueResults;
    /*
     * Mark the plan whether includes delta table or not.
     */
    bool isDeltaTable;

    /* used to replace work_mem, maxmem in [0], and minmem in [1] */
    int operatorMemKB[2];
    /* allowed max mem after spread */
    int operatorMaxMem;

    bool parallel_enabled; /* Is it run in parallel? */
    bool hasHashFilter;    /* true for this plan has a hashfilter */

    List* var_list;        /* Need bloom filter var list. */
    List* filterIndexList; /* Need used bloomfilter array index. */

    /* used to replace work_mem */
    int** ng_operatorMemKBArray; /* for multiple logic cluster */
    int ng_num;
    double innerdistinct; /* join inner rel distinct estimation value */
    double outerdistinct; /* join outer rel distinct estimation value */
} Plan;

typedef struct Path {
    NodeTag type;

    NodeTag pathtype; /* tag identifying scan/join method */

    RelOptInfo* parent;        /* the relation this path can build */
    ParamPathInfo* param_info; /* parameterization info, or NULL if none */

    /* estimated size/costs for path (see costsize.c for more info) */
    double rows; /* estimated number of global result tuples */
    double multiple;
    Cost startup_cost; /* cost expended before fetching any tuples */
    Cost total_cost;   /* total cost (assuming all tuples fetched) */
    Cost stream_cost;  /* cost of actions invoked by stream but can't be parallelled in this path */

    List* pathkeys;        /* sort ordering of path's output */
    List* distribute_keys; /* distribute key, Var list */
    char locator_type;
    Oid rangelistOid;
    int dop; /* degree of parallelism */
    /* pathkeys is a List of PathKey nodes; see above */
    Distribution distribution;
    int hint_value;       /* Mark this path if be hinted, and hint kind. */
    double innerdistinct; /* join inner rel distinct estimation value */
    double outerdistinct; /* join outer rel distinct estimation value */
} Path;

typedef struct PlannerInfo {
    NodeTag type;

    Query* parse; /* the Query being planned */

    PlannerGlobal* glob; /* global info for current planner run */

    Index query_level; /* 1 at the outermost Query */

    struct PlannerInfo* parent_root; /* NULL at outermost Query */

    /*
     * simple_rel_array holds pointers to "base rels" and "other rels" (see
     * comments for RelOptInfo for more info).  It is indexed by rangetable
     * index (so entry 0 is always wasted).  Entries can be NULL when an RTE
     * does not correspond to a base relation, such as a join RTE or an
     * unreferenced view RTE; or if the RelOptInfo hasn't been made yet.
     */
    struct RelOptInfo** simple_rel_array; /* All 1-rel RelOptInfos */
    int simple_rel_array_size;            /* allocated size of array */

    /*
     * List of changed var that mutated during cost-based rewrite optimization, the
     * element in the list is "struct RewriteVarMapping", for example:
     * - inlist2join
     * - pushjoin2union (will implemented)
     * _ ...
     *
     */
    List* var_mappings;
    Relids var_mapping_rels; /* all the relations that related to inlist2join */

    /*
     * simple_rte_array is the same length as simple_rel_array and holds
     * pointers to the associated rangetable entries.  This lets us avoid
     * rt_fetch(), which can be a bit slow once large inheritance sets have
     * been expanded.
     */
    RangeTblEntry** simple_rte_array; /* rangetable as an array */

    /*
     * all_baserels is a Relids set of all base relids (but not "other"
     * relids) in the query; that is, the Relids identifier of the final join
     * we need to form.
     */
    Relids all_baserels;

    /*
     * join_rel_list is a list of all join-relation RelOptInfos we have
     * considered in this planning run.  For small problems we just scan the
     * list to do lookups, but when there are many join relations we build a
     * hash table for faster lookups.  The hash table is present and valid
     * when join_rel_hash is not NULL.  Note that we still maintain the list
     * even when using the hash table for lookups; this simplifies life for
     * GEQO.
     */
    List* join_rel_list;        /* list of join-relation RelOptInfos */
    struct HTAB* join_rel_hash; /* optional hashtable for join relations */

    /*
     * When doing a dynamic-programming-style join search, join_rel_level[k]
     * is a list of all join-relation RelOptInfos of level k, and
     * join_cur_level is the current level.  New join-relation RelOptInfos are
     * automatically added to the join_rel_level[join_cur_level] list.
     * join_rel_level is NULL if not in use.
     */
    List** join_rel_level; /* lists of join-relation RelOptInfos */
    int join_cur_level;    /* index of list being extended */

    List* init_plans; /* init SubPlans for query */

    List* cte_plan_ids; /* per-CTE-item list of subplan IDs */

    List* eq_classes; /* list of active EquivalenceClasses */

    List* canon_pathkeys; /* list of "canonical" PathKeys */

    List* left_join_clauses; /* list of RestrictInfos for
                              * mergejoinable outer join clauses
                              * w/nonnullable var on left */

    List* right_join_clauses; /* list of RestrictInfos for
                               * mergejoinable outer join clauses
                               * w/nonnullable var on right */

    List* full_join_clauses; /* list of RestrictInfos for
                              * mergejoinable full join clauses */

    List* join_info_list; /* list of SpecialJoinInfos */

    List* lateral_info_list;  /* list of LateralJoinInfos */

    List* append_rel_list; /* list of AppendRelInfos */

    List* rowMarks; /* list of PlanRowMarks */

    List* placeholder_list; /* list of PlaceHolderInfos */

    List* query_pathkeys; /* desired pathkeys for query_planner(), and
                           * actual pathkeys afterwards */

    List* group_pathkeys;    /* groupClause pathkeys, if any */
    List* window_pathkeys;   /* pathkeys of bottom window, if any */
    List* distinct_pathkeys; /* distinctClause pathkeys, if any */
    List* sort_pathkeys;     /* sortClause pathkeys, if any */

    List* minmax_aggs; /* List of MinMaxAggInfos */

    List* initial_rels; /* RelOptInfos we are now trying to join */

    MemoryContext planner_cxt; /* context holding PlannerInfo */

    double total_table_pages; /* # of pages in all tables of query */

    double tuple_fraction; /* tuple_fraction passed to query_planner */
    double limit_tuples;   /* limit_tuples passed to query_planner */

    bool hasInheritedTarget;     /* true if parse->resultRelation is an
                                  * inheritance child rel */
    bool hasJoinRTEs;            /* true if any RTEs are RTE_JOIN kind */
    bool hasLateralRTEs;         /* true if any RTEs are marked LATERAL */
    bool hasHavingQual;          /* true if havingQual was non-null */
    bool hasPseudoConstantQuals; /* true if any RestrictInfo has
                                  * pseudoconstant = true */
    bool hasRecursion;           /* true if planning a recursive WITH item */

    /* Note: qualSecurityLevel is zero if there are no securityQuals */
    Index qualSecurityLevel; /* minimum security_level for quals */

#ifdef PGXC
    /* This field is used only when RemoteScan nodes are involved */
    int rs_alias_index; /* used to build the alias reference */

    /*
     * In Postgres-XC Coordinators are supposed to skip the handling of
     * row marks of type ROW_MARK_EXCLUSIVE & ROW_MARK_SHARE.
     * In order to do that we simply remove such type
     * of row marks from the list rowMarks. Instead they are saved
     * in xc_rowMarks list that is then handeled to add
     * FOR UPDATE/SHARE in the remote query
     */
    List* xc_rowMarks; /* list of PlanRowMarks of type ROW_MARK_EXCLUSIVE & ROW_MARK_SHARE */
#endif

    /* These fields are used only when hasRecursion is true: */
    int wt_param_id;                 /* PARAM_EXEC ID for the work table */
    struct Plan* non_recursive_plan; /* plan for non-recursive term */

    /* These fields are workspace for createplan.c */
    Relids curOuterRels;  /* outer rels above current node */
    List* curOuterParams; /* not-yet-assigned NestLoopParams */

    Index curIteratorParamIndex;
    bool isPartIteratorPlanning;
    int curItrs;
    List* subqueryRestrictInfo; /* Subquery RestrictInfo, which only be used in wondows agg. */

    /* optional private data for join_search_hook, e.g., GEQO */
    void* join_search_private;

    /* Added post-release, will be in a saner place in 9.3: */
    List* plan_params; /* list of PlannerParamItems, see below */

    /* For count_distinct, save null info for group by clause */
    List* join_null_info;
    /* for GroupingFunc fixup in setrefs */
    AttrNumber* grouping_map;

    /* If current query level is correlated with upper level */
    bool is_correlated;

    /* data redistribution for DFS table.
     * dataDestRelIndex is index into the range table. This variable
     * will take effect on data redistribution state.
     * The effective value must be greater than 0.
     */
    Index dataDestRelIndex;

    /* interesting keys of current query level */
    ItstDisKey dis_keys;

    /*
     * indicate if the subquery planning root (PlannerInfo) is under or rooted from
     * recursive-cte planning.
     */
    bool is_under_recursive_cte;

    /*
     * indicate if the subquery planning root (PlannerInfo) is under recursive-cte's
     * recursive branch
     */
    bool is_under_recursive_tree;
    bool has_recursive_correlated_rte; /* true if any RTE correlated with recursive cte */

    int subquery_type;
    Bitmapset *param_upper;
    
    bool hasRownumQual;
} PlannerInfo;

typedef struct PlannedStmt {
    NodeTag type;

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

    uint64 queryId; /* query identifier,  uniquely indicate this plan in Runtime (copied from Query) */

    bool hasReturning; /* is it insert|update|delete RETURNING? */

    bool hasModifyingCTE; /* has insert|update|delete in WITH? */

    bool canSetTag; /* do I set the command result tag? */

    bool transientPlan; /* redo plan when TransactionXmin changes? */

    bool dependsOnRole; /* is plan specific to current role? */

    Plan* planTree; /* tree of Plan nodes */

    List* rtable; /* list of RangeTblEntry nodes */

    /* rtable indexes of target relations for INSERT/UPDATE/DELETE */
    List* resultRelations; /* integer list of RT indexes, or NIL */

    Node* utilityStmt; /* non-null if this is DECLARE CURSOR */

    List* subplans; /* Plan trees for SubPlan expressions */

    Bitmapset* rewindPlanIDs; /* indices of subplans that require REWIND */

    List* rowMarks; /* a list of PlanRowMark's */

    /*
     * Notice: be careful to use relationOids as it may contain non-table OID
     * in some scenarios, e.g. assignment of relationOids in fix_expr_common.
     */
    List* relationOids; /* contain OIDs of relations the plan depends on */

    List* invalItems; /* other dependencies, as PlanInvalItems */

    int nParamExec; /* number of PARAM_EXEC Params used */

    int num_streams; /* number of stream exist in plan tree */

    int max_push_sql_num; /* number of split sql want push DN execute */

    int gather_count; /* gather_count in query */

    int num_nodes; /* number of data nodes */

    NodeDefinition* nodesDefinition; /* all data nodes' defination */

    int instrument_option; /* used for collect instrument data */

    int num_plannodes; /* how many plan node in this planstmt */

    int query_mem[2]; /* how many memory the query can use ,  memory in kb  */

    int assigned_query_mem[2]; /* how many memory the query is assigned   */

    bool is_dynmaic_smp;

    int dynsmp_max_cpu; /* max avaliable cpu for this dn */

    int dynsmp_avail_cpu; /* max avaliable cpu for this dn */

    int dynsmp_cpu_util;

    int dynsmp_active_statement;

    double dynsmp_query_estimate_cpu_usge;

    int dynsmp_plan_optimal_dop; /* the final optimized dop for the plan */

    int dynsmp_plan_original_dop;

    int dynsmp_dop_mem_limit; /* memory will put a limit on dop */

    int dynsmp_min_non_spill_dop; /* optimal dop cannot greater than this */

    int num_bucketmaps; /* Num of special-bucketmap stored in plannedstmt */

    uint2* bucketMap[MAX_SPECIAL_BUCKETMAP_NUM]; /* the map information need to be get */

    char* query_string; /* convey the query string to backend/stream thread of DataNode for debug purpose */

    List* subplan_ids; /* in which plan id subplan should be inited */

    List* initPlan; /* initplan in top plan node */
    /* data redistribution for DFS table.
     * dataDestRelIndex is index into the range table. This variable
     * will take effect on data redistribution state.
     */
    Index dataDestRelIndex;

    int MaxBloomFilterNum;

    int query_dop; /* Dop of current query. */

    double plannertime; /* planner execute time */

    /* set true in do_query_for_planrouter() for PlannedStmt sent to
     * the compute pool
     */
    bool in_compute_pool;

    /* true if there is/are ForeignScan node(s) of OBS foreign table
     * in plantree.
     */
    bool has_obsrel;

    List* plan_hint_warning; /* hint warning during plan generation, only used in CN */

    List* noanalyze_rellist; /* relations and attributes that have no statistics, only used in CN */

    int ng_num;                     /* nodegroup number */
    NodeGroupQueryMem* ng_queryMem; /* each nodegroup's query mem */
    bool ng_use_planA;              /* true means I am a planA, default false */

    bool isRowTriggerShippable; /* true if all row triggers are shippable. */
    bool is_stream_plan;
    bool multi_node_hint;

    uint64 uniqueSQLId;
} PlannedStmt;

相关代码在planner.cpp, planmain.cpp,主要流程如下

void query_planner(PlannerInfo* root, List* tlist, double tuple_fraction, double limit_tuples, Path** cheapest_path,
    Path** sorted_path, double* num_groups, List* rollup_groupclauses, List* rollup_lists)
{
    ...
    /*
     * If the query has an empty join tree, then it's something easy like
     * "SELECT 2+2;" or "INSERT ... VALUES()".  Fall through quickly.
     */
    // 空连接树语句处理
    if (parse->jointree->fromlist == NIL) {
        /* We need a trivial path result */
        *cheapest_path = (Path*)create_result_path(root, NULL, (List*)parse->jointree->quals);
        *sorted_path = NULL;

        /*
         * We still are required to canonicalize any pathkeys, in case it's
         * something like "SELECT 2+2 ORDER BY 1".
         */
        root->canon_pathkeys = NIL;
        canonicalize_all_pathkeys(root);
        return;
    }

    ...
    // 准备用于快速访问基本关系的数组
    setup_simple_rel_arrays(root);

    // 为所有基本关系构造一个新的RelOptInfo
    add_base_rels_to_query(root, (Node*)parse->jointree);
    check_scan_hint_validity(root);

    // 给targetlists增加条目,生成PlaceHolderInfo条目,为可证明的等价表达式构建了等价类,最后创建目标连接
    build_base_rel_tlists(root, tlist);

    find_placeholders_in_jointree(root);

    find_lateral_references(root);

    joinlist = deconstruct_jointree(root);

    ...
    // 由等价类重新考虑任何延迟的外连接条件
    reconsider_outer_join_clauses(root);

    // 对等价类,生成额外的约束子句。
    generate_base_implied_equalities(root);

    generate_base_implied_qualities(root);

    // 路径键规范化
    canonicalize_all_pathkeys(root);

    // 检查placeholder表达式
    fix_placeholder_input_needed_levels(root);

    // 移除不需要的外连接
    joinlist = remove_useless_joins(root, joinlist);

    // 分配placeholders到基础关系
    add_placeholders_to_base_rels(root);

    // 计算total_table_pages 
    total_pages = 0;
    for (rti = 1; rti < (unsigned int)root->simple_rel_array_size; rti++) {
        RelOptInfo* brel = root->simple_rel_array[rti];

        if (brel == NULL)
            continue;

        AssertEreport(brel->relid == rti,
            MOD_OPT,
            "invalid relation oid when generating a path for a basic query."); /* sanity check on array */

        if (brel->reloptkind == RELOPT_BASEREL || brel->reloptkind == RELOPT_OTHER_MEMBER_REL)
            total_pages += (double)brel->pages;
    }
    root->total_table_pages = total_pages;

    // 查找执行查询的所有可能的访问路径,返回表示查询中所有基本关系的连接的单个关系
    final_rel = make_one_rel(root, joinlist);
    ...
    if (parse->groupClause) { // 估算分组结果组的数量 
        ...
    } else if (parse->hasAggs || root->hasHavingQual || parse->groupingSets) {
        // 未分组的聚合读取所有元组
        ...
    } else if (parse->distinctClause) {
        // 未分组未聚合估算结果行
        ...
    } else {
        // 简单的非分组非聚合查询:计算tuple fraction
        ...
    }

    // 选择最廉价的路径
    cheapestpath = get_cheapest_path(root, final_rel, num_groups, has_groupby);

    ...
    *cheapest_path = cheapestpath;
    *sorted_path = sortedpath;
}
  • 执行器
    相关数据结构
typedef struct QueryDesc {
    /* These fields are provided by CreateQueryDesc */
    CmdType operation;            /* CMD_SELECT, CMD_UPDATE, etc. */
    PlannedStmt* plannedstmt;     /* planner's output, or null if utility */
    Node* utilitystmt;            /* utility statement, or null */
    const char* sourceText;       /* source text of the query */
    Snapshot snapshot;            /* snapshot to use for query */
    Snapshot crosscheck_snapshot; /* crosscheck for RI update/delete */
    DestReceiver* dest;           /* the destination for tuple output */
    ParamListInfo params;         /* param values being passed in */
    int instrument_options;       /* OR of InstrumentOption flags */

    /* These fields are set by ExecutorStart */
    TupleDesc tupDesc;    /* descriptor for result tuples */
    EState* estate;       /* executor's query-wide state */
    PlanState* planstate; /* tree of per-plan-node state */

    /* This is always set NULL by the core system, but plugins can change it */
    struct Instrumentation* totaltime; /* total time spent in ExecutorRun */
    bool executed;                     /* if the query already executed */
#ifdef ENABLE_MOT
    JitExec::JitContext* mot_jit_context;   /* MOT JIT context required for executing LLVM jitted code */
#endif
} QueryDesc;

相关代码在execMain.cpp

// 执行器启动
void ExecutorStart(QueryDesc* queryDesc, int eflags)
{
    gstrace_entry(GS_TRC_ID_ExecutorStart);

    /* it's unsafe to deal with plugins hooks as dynamic lib may be released */
    if (ExecutorStart_hook && !(g_instance.status > NoShutdown))
        (*ExecutorStart_hook)(queryDesc, eflags);
    else
        standard_ExecutorStart(queryDesc, eflags);

    gstrace_exit(GS_TRC_ID_ExecutorStart);
}
// 执行器执行
void ExecutorRun(QueryDesc *queryDesc, ScanDirection direction, long count)
{
    ...
    // SQL自调优:查询执行结束后,根据运行时信息分析查询计划问题
    if (u_sess->exec_cxt.need_track_resource && queryDesc != NULL && has_track_operator &&
        (IS_PGXC_COORDINATOR || IS_SINGLE_NODE)) {
        List *issue_results = PlanAnalyzerOperator(queryDesc, queryDesc->planstate);

        // 如果发现计划问题,将其存储在sysview gs_wlm_session_history中
        if (issue_results != NIL) {
            RecordQueryPlanIssues(issue_results);
        }
    }
    ...
    // SQL动态特性,操作符历史统计
    if (can_operator_history_statistics) {
        u_sess->instr_cxt.can_record_to_table = true;
        ExplainNodeFinish(queryDesc->planstate, queryDesc->plannedstmt, GetCurrentTimestamp(), false);

        if ((IS_PGXC_COORDINATOR) && u_sess->instr_cxt.global_instr != NULL) {
            delete u_sess->instr_cxt.global_instr;
            u_sess->instr_cxt.thread_instr = NULL;
            u_sess->instr_cxt.global_instr = NULL;
        }
    }

    u_sess->pcache_cxt.cur_stmt_name = old_stmt_name;
}
// 执行器完成
void ExecutorFinish(QueryDesc *queryDesc)
{
    if (ExecutorFinish_hook) {
        (*ExecutorFinish_hook)(queryDesc);
    } else {
        standard_ExecutorFinish(queryDesc);
    }
}
// 执行器结束
void ExecutorEnd(QueryDesc *queryDesc)
{
    if (ExecutorEnd_hook) {
        (*ExecutorEnd_hook)(queryDesc);
    } else {
        standard_ExecutorEnd(queryDesc);
    }
}

PostgresMain.cpp ReadyForQuery刷出执行结果到客户端。

由以上可得出简单查询的相关函数如下图:


Select.png

你可能感兴趣的:(语句的执行流程)