pg_plan_queri..."/>
溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

PostgreSQL 源碼解讀(75)- 查詢語(yǔ)句#60(Review - standard_...

發(fā)布時(shí)間:2020-08-09 19:49:32 來(lái)源:ITPUB博客 閱讀:205 作者:husthxd 欄目:關(guān)系型數(shù)據(jù)庫(kù)

本節(jié)Review standard_planner函數(shù)的實(shí)現(xiàn)邏輯,該函數(shù)查詢優(yōu)化器的主入口。

一、源碼解讀

standard_planner函數(shù)由exec_simple_query->pg_plan_queries->pg_plan_query->planner函數(shù)調(diào)用,其調(diào)用棧如下:

(gdb) b planner
Breakpoint 1 at 0x7c7442: file planner.c, line 259.
(gdb) c
Continuing.

Breakpoint 1, planner (parse=0x2eee0b0, cursorOptions=256, boundParams=0x0) at planner.c:259
259     if (planner_hook)
(gdb) bt
#0  planner (parse=0x2eee0b0, cursorOptions=256, boundParams=0x0) at planner.c:259
#1  0x00000000008c5824 in pg_plan_query (querytree=0x2eee0b0, cursorOptions=256, boundParams=0x0) at postgres.c:809
#2  0x00000000008c595d in pg_plan_queries (querytrees=0x2f10ac8, cursorOptions=256, boundParams=0x0) at postgres.c:875
#3  0x00000000008c5c35 in exec_simple_query (query_string=0x2eed1a8 "select * from t1;") at postgres.c:1050
#4  0x00000000008ca0f8 in PostgresMain (argc=1, argv=0x2f16cb8, dbname=0x2f16b20 "testdb", username=0x2ee9d48 "xdb")
    at postgres.c:4159
#5  0x0000000000825880 in BackendRun (port=0x2f0eb00) at postmaster.c:4361
#6  0x0000000000824fe4 in BackendStartup (port=0x2f0eb00) at postmaster.c:4033
#7  0x0000000000821371 in ServerLoop () at postmaster.c:1706
#8  0x0000000000820c09 in PostmasterMain (argc=1, argv=0x2ee7d00) at postmaster.c:1379
#9  0x0000000000747ea7 in main (argc=1, argv=0x2ee7d00) at main.c:228

該函數(shù)根據(jù)輸入的查詢樹(Query)生成已規(guī)劃的語(yǔ)句,可用于執(zhí)行器實(shí)際執(zhí)行.

/*****************************************************************************
 *
 *     Query optimizer entry point
 *     查詢優(yōu)化器入口
 *
 * To support loadable plugins that monitor or modify planner behavior,
 * we provide a hook variable that lets a plugin get control before and
 * after the standard planning process.  The plugin would normally call
 * standard_planner().
 * 在調(diào)用標(biāo)準(zhǔn)的規(guī)劃過(guò)程前后提供了鉤子函數(shù)插件,可以用于監(jiān)控或者改變優(yōu)化器的行為.
 * 
 * Note to plugin authors: standard_planner() scribbles on its Query input,
 * so you'd better copy that data structure if you want to plan more than once.
 * 開(kāi)發(fā)插件時(shí)需要注意:standard_planner()函數(shù)會(huì)修改輸入?yún)?shù),如果想要多次規(guī)劃,最好復(fù)制輸入的數(shù)據(jù)結(jié)構(gòu)
 * 
 *****************************************************************************/
PlannedStmt *
planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
{
    PlannedStmt *result;

    if (planner_hook)//鉤子函數(shù)
        result = (*planner_hook) (parse, cursorOptions, boundParams);
    else
        result = standard_planner(parse, cursorOptions, boundParams);
    return result;
}

PlannedStmt *
standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)//標(biāo)準(zhǔn)優(yōu)化過(guò)程
{
    PlannedStmt *result;//返回結(jié)果
    PlannerGlobal *glob;//全局的Plan信息-Global information for planning/optimization
    double      tuple_fraction;//
    PlannerInfo *root;//每個(gè)Query的Plan信息-Per-query information for planning/optimization
    RelOptInfo *final_rel;//Plan中的每個(gè)Relation信息-Per-relation information for planning/optimization
    Path       *best_path;//最優(yōu)路徑
    Plan       *top_plan;//最上層的Plan
    ListCell   *lp,//臨時(shí)變量
               *lr;

    /*
     * Set up global state for this planner invocation.  This data is needed
     * across all levels of sub-Query that might exist in the given command,
     * so we keep it in a separate struct that's linked to by each per-Query
     * PlannerInfo.
     * 為這個(gè)規(guī)劃器的調(diào)用設(shè)置全局狀態(tài)。
     * 由于在給定的命令中可能存在的所有級(jí)別的子查詢中都需要此數(shù)據(jù)結(jié)構(gòu),
     * 因此我們將其保存在一個(gè)單獨(dú)的結(jié)構(gòu)中,每個(gè)查詢PlannerInfo都鏈接到這個(gè)結(jié)構(gòu)中。
     */
    glob = makeNode(PlannerGlobal);//構(gòu)建PlannerGlobal
     //初始化參數(shù)
    glob->boundParams = boundParams;
    glob->subplans = NIL;
    glob->subroots = NIL;
    glob->rewindPlanIDs = NULL;
    glob->finalrtable = NIL;
    glob->finalrowmarks = NIL;
    glob->resultRelations = NIL;
    glob->nonleafResultRelations = NIL;
    glob->rootResultRelations = NIL;
    glob->relationOids = NIL;
    glob->invalItems = NIL;
    glob->paramExecTypes = NIL;
    glob->lastPHId = 0;
    glob->lastRowMarkId = 0;
    glob->lastPlanNodeId = 0;
    glob->transientPlan = false;
    glob->dependsOnRole = false;

    /*
     * Assess whether it's feasible to use parallel mode for this query. We
     * can't do this in a standalone backend, or if the command will try to
     * modify any data, or if this is a cursor operation, or if GUCs are set
     * to values that don't permit parallelism, or if parallel-unsafe
     * functions are present in the query tree.
     * 評(píng)估對(duì)這個(gè)查詢使用并行模式是否可行。
     * 我們不能在獨(dú)立的后臺(tái)過(guò)程中進(jìn)行此操作,或者在可以修改數(shù)據(jù)的命令種執(zhí)行此操作,
     * 或者說(shuō)如果是涉及游標(biāo)的操作,或者GUCs設(shè)置為不允許并行的值,或者查詢樹中存在不安全的并行函數(shù),均不允許。
     *
     * (Note that we do allow CREATE TABLE AS, SELECT INTO, and CREATE
     * MATERIALIZED VIEW to use parallel plans, but this is safe only because
     * the command is writing into a completely new table which workers won't
     * be able to see.  If the workers could see the table, the fact that
     * group locking would cause them to ignore the leader's heavyweight
     * relation extension lock and GIN page locks would make this unsafe.
     * We'll have to fix that somehow if we want to allow parallel inserts in
     * general; updates and deletes have additional problems especially around
     * combo CIDs.)
     * (請(qǐng)注意,我們確實(shí)允許CREATE TABLE AS、SELECT INTO和CREATE MATERIALIZED VIEW來(lái)使用并行計(jì)劃,
     * 但是這是安全的,因?yàn)槊顚懙搅艘粋€(gè)全新的表中,并行處理過(guò)程worker看不到。
     * 如果工workers能看到這個(gè)表,那么group locking會(huì)導(dǎo)致它們忽略leader的重量級(jí)關(guān)系擴(kuò)展鎖和GIN page鎖,這會(huì)變得不安全。
     * 如果希望做到并行插入,那么就必須解決這個(gè)問(wèn)題;另外,更新和刪除有存在額外的問(wèn)題,特別是關(guān)于組合CIDs。
     *
     * For now, we don't try to use parallel mode if we're running inside a
     * parallel worker.  We might eventually be able to relax this
     * restriction, but for now it seems best not to have parallel workers
     * trying to create their own parallel workers.
     * 目前,如果在一個(gè)并行worker內(nèi)部運(yùn)行,則不會(huì)嘗試使用并行模式。
     * 可能最終能夠放寬這一限制,但就目前而言,最好不要讓worker試圖創(chuàng)建它們自己的worker。
     *
     * We can't use parallelism in serializable mode because the predicate
     * locking code is not parallel-aware.  It's not catastrophic if someone
     * tries to run a parallel plan in serializable mode; it just won't get
     * any workers and will run serially.  But it seems like a good heuristic
     * to assume that the same serialization level will be in effect at plan
     * time and execution time, so don't generate a parallel plan if we're in
     * serializable mode.
     * 在serializable事務(wù)模式下,不能使用并行,因?yàn)橹^詞鎖定代碼不支持并行。
     * 如果有人試圖在serializable模式下運(yùn)行并行計(jì)劃,雖然這不是災(zāi)難性的,但它不會(huì)得到worker,而是會(huì)串行運(yùn)行。
     * 但是,假設(shè)相同的serializable級(jí)別將在計(jì)劃時(shí)和執(zhí)行時(shí)生效,這似乎是一個(gè)很好的啟發(fā),
     * 因此,如果我們處于serializable模式,則不要生成并行計(jì)劃。
     */
    if ((cursorOptions & CURSOR_OPT_PARALLEL_OK) != 0 &&
        IsUnderPostmaster &&    
        parse->commandType == CMD_SELECT &&
        !parse->hasModifyingCTE &&
        max_parallel_workers_per_gather > 0 &&
        !IsParallelWorker() &&
        !IsolationIsSerializable())//并行模式的判斷
    {
        /* all the cheap tests pass, so scan the query tree */
        glob->maxParallelHazard = max_parallel_hazard(parse);
        glob->parallelModeOK = (glob->maxParallelHazard != PROPARALLEL_UNSAFE);
    }
    else
    {
        /* skip the query tree scan, just assume it's unsafe */
        glob->maxParallelHazard = PROPARALLEL_UNSAFE;
        glob->parallelModeOK = false;
    }

    /*
     * glob->parallelModeNeeded is normally set to false here and changed to
     * true during plan creation if a Gather or Gather Merge plan is actually
     * created (cf. create_gather_plan, create_gather_merge_plan).
     * 如果確實(shí)創(chuàng)建了一個(gè)聚合或聚合合并計(jì)劃,則通常將glob->parallelModeNeeded設(shè)置為false,
     * 并在計(jì)劃創(chuàng)建期間更改為true (cf. create_gather_plan, create_gather_merge_plan)。
     * 
     * However, if force_parallel_mode = on or force_parallel_mode = regress,
     * then we impose parallel mode whenever it's safe to do so, even if the
     * final plan doesn't use parallelism.  It's not safe to do so if the
     * query contains anything parallel-unsafe; parallelModeOK will be false
     * in that case.  Note that parallelModeOK can't change after this point.
     * Otherwise, everything in the query is either parallel-safe or
     * parallel-restricted, and in either case it should be OK to impose
     * parallel-mode restrictions.  If that ends up breaking something, then
     * either some function the user included in the query is incorrectly
     * labelled as parallel-safe or parallel-restricted when in reality it's
     * parallel-unsafe, or else the query planner itself has a bug.
     * 但是,如果force_parallel_mode = on或force_parallel_mode = regress,
     * 那么只要安全,我們就強(qiáng)制執(zhí)行并行模式,即使最終計(jì)劃不使用并行。
     * 如果查詢包含任何不安全的內(nèi)容,那么這樣做是不安全的;在這種情況下,parallelModeOK將為false。
     * 注意,在這一點(diǎn)之后,parallelModeOK無(wú)法更改。
     * 否則,查詢中的所有內(nèi)容要么是并行安全的,要么是并行限制的,
     * 在任何一種情況下,都應(yīng)該可以施加并行模式限制。
     * 如果這最終出現(xiàn)了問(wèn)題,要么查詢中包含的某個(gè)函數(shù)被錯(cuò)誤地標(biāo)記為并行安全或并行限制(實(shí)際它是并行不安全的),
     * 要么查詢規(guī)劃器本身有錯(cuò)誤。
     */
    glob->parallelModeNeeded = glob->parallelModeOK &&
        (force_parallel_mode != FORCE_PARALLEL_OFF);

    /* Determine what fraction of the plan is likely to be scanned */
    //確定掃描比例
    if (cursorOptions & CURSOR_OPT_FAST_PLAN)
    {
        /*
         * We have no real idea how many tuples the user will ultimately FETCH
         * from a cursor, but it is often the case that he doesn't want 'em
         * all, or would prefer a fast-start plan anyway so that he can
         * process some of the tuples sooner.  Use a GUC parameter to decide
         * what fraction to optimize for.
         * 我們不知道用戶最終會(huì)從游標(biāo)中獲取多少元組,但通常情況下,用戶不需要所有元組,
         * 或者更喜歡快速啟動(dòng)計(jì)劃,以便更快地處理一些元組。
         * 使用GUC參數(shù)來(lái)決定優(yōu)化哪個(gè)分?jǐn)?shù)。
         */
        tuple_fraction = cursor_tuple_fraction;//使用GUC 參數(shù)

        /*
         * We document cursor_tuple_fraction as simply being a fraction, which
         * means the edge cases 0 and 1 have to be treated specially here.  We
         * convert 1 to 0 ("all the tuples") and 0 to a very small fraction.
         * 我們將cursor_tuple_fraction記錄為一個(gè)簡(jiǎn)單的分?jǐn)?shù),這意味著邊界情況0和1必須在這里特別處理。
         * 我們將1轉(zhuǎn)換為0(“所有元組”),將0轉(zhuǎn)換為非常小的分?jǐn)?shù)。
         */
        if (tuple_fraction >= 1.0)
            tuple_fraction = 0.0;
        else if (tuple_fraction <= 0.0)
            tuple_fraction = 1e-10;
    }
    else
    {
        /* Default assumption is we need all the tuples */
        //默認(rèn)假設(shè):需要所有元組
        tuple_fraction = 0.0;
    }

    /* primary planning entry point (may recurse for subqueries) */
    //主規(guī)劃過(guò)程入口(可能會(huì)遞歸執(zhí)行)
    root = subquery_planner(glob, parse, NULL,
                            false, tuple_fraction);//獲取PlannerInfo根節(jié)點(diǎn)

    /* Select best Path and turn it into a Plan */
    //選擇最優(yōu)路徑并把該路徑轉(zhuǎn)換為執(zhí)行計(jì)劃
    final_rel = fetch_upper_rel(root, UPPERREL_FINAL, NULL);//獲取頂層的RelOptInfo
    best_path = get_cheapest_fractional_path(final_rel, tuple_fraction);//選擇最佳路徑

    top_plan = create_plan(root, best_path);//生成執(zhí)行計(jì)劃

    /*
     * If creating a plan for a scrollable cursor, make sure it can run
     * backwards on demand.  Add a Material node at the top at need.
     * 如為可滾動(dòng)游標(biāo)創(chuàng)建計(jì)劃,確保它可以根據(jù)需要向后運(yùn)行。
     * 在需要時(shí)在最外層添加一個(gè)物化(Material)節(jié)點(diǎn)。
     */
    if (cursorOptions & CURSOR_OPT_SCROLL)
    {
        if (!ExecSupportsBackwardScan(top_plan))
            top_plan = materialize_finished_plan(top_plan);
    }

    /*
     * Optionally add a Gather node for testing purposes, provided this is
     * actually a safe thing to do.
     * 為了測(cè)試的目的,可以選擇添加一個(gè)Gather節(jié)點(diǎn),前提是這樣做實(shí)際上是安全的。
     */
    if (force_parallel_mode != FORCE_PARALLEL_OFF && top_plan->parallel_safe)
    {
        Gather     *gather = makeNode(Gather);

        /*
         * If there are any initPlans attached to the formerly-top plan node,
         * move them up to the Gather node; same as we do for Material node in
         * materialize_finished_plan.
         * 如果有任何initplan連接到formerly-top的計(jì)劃節(jié)點(diǎn),將它們移動(dòng)到Gather節(jié)點(diǎn);
         * 這與我們?cè)趍aterialize_finished_plan中的Material節(jié)點(diǎn)相同。
         */
        gather->plan.initPlan = top_plan->initPlan;
        top_plan->initPlan = NIL;

        gather->plan.targetlist = top_plan->targetlist;
        gather->plan.qual = NIL;
        gather->plan.lefttree = top_plan;
        gather->plan.righttree = NULL;
        gather->num_workers = 1;
        gather->single_copy = true;
        gather->invisible = (force_parallel_mode == FORCE_PARALLEL_REGRESS);

        /*
         * Since this Gather has no parallel-aware descendants to signal to,
         * we don't need a rescan Param.
         * 因?yàn)檫@個(gè)Gather沒(méi)有parallel-aware的后代信號(hào),所以我們不需要重新掃描參數(shù)。
         */
        gather->rescan_param = -1;

        /*
         * Ideally we'd use cost_gather here, but setting up dummy path data
         * to satisfy it doesn't seem much cleaner than knowing what it does.
         */
        gather->plan.startup_cost = top_plan->startup_cost +
            parallel_setup_cost;
        gather->plan.total_cost = top_plan->total_cost +
            parallel_setup_cost + parallel_tuple_cost * top_plan->plan_rows;
        gather->plan.plan_rows = top_plan->plan_rows;
        gather->plan.plan_width = top_plan->plan_width;
        gather->plan.parallel_aware = false;
        gather->plan.parallel_safe = false;

        /* use parallel mode for parallel plans. */
        //使用并行模式
        root->glob->parallelModeNeeded = true;

        top_plan = &gather->plan;
    }

    /*
     * If any Params were generated, run through the plan tree and compute
     * each plan node's extParam/allParam sets.  Ideally we'd merge this into
     * set_plan_references' tree traversal, but for now it has to be separate
     * because we need to visit subplans before not after main plan.
     * 如果生成了Params,運(yùn)行計(jì)劃樹并計(jì)算每個(gè)計(jì)劃節(jié)點(diǎn)的extParam/allParam集合。
     * 理想情況下,我們應(yīng)該將其合并到set_plan_references的樹遍歷中,
     * 但是現(xiàn)在它必須是獨(dú)立的,因?yàn)樾枰谥饔?jì)劃之前而不是之后訪問(wèn)子計(jì)劃。
     */
    if (glob->paramExecTypes != NIL)
    {
        Assert(list_length(glob->subplans) == list_length(glob->subroots));
        forboth(lp, glob->subplans, lr, glob->subroots)
        {
            Plan       *subplan = (Plan *) lfirst(lp);
            PlannerInfo *subroot = lfirst_node(PlannerInfo, lr);

            SS_finalize_plan(subroot, subplan);
        }
        SS_finalize_plan(root, top_plan);
    }

    /* final cleanup of the plan */
    //最后的清理工作
    Assert(glob->finalrtable == NIL);
    Assert(glob->finalrowmarks == NIL);
    Assert(glob->resultRelations == NIL);
    Assert(glob->nonleafResultRelations == NIL);
    Assert(glob->rootResultRelations == NIL);
    top_plan = set_plan_references(root, top_plan);
    /* ... and the subplans (both regular subplans and initplans) */
    Assert(list_length(glob->subplans) == list_length(glob->subroots));
    forboth(lp, glob->subplans, lr, glob->subroots)
    {
        Plan       *subplan = (Plan *) lfirst(lp);
        PlannerInfo *subroot = lfirst_node(PlannerInfo, lr);

        lfirst(lp) = set_plan_references(subroot, subplan);
    }

    /* build the PlannedStmt result */
    //構(gòu)建PlannedStmt結(jié)構(gòu)
    result = makeNode(PlannedStmt);

    result->commandType = parse->commandType;//命令類型
    result->queryId = parse->queryId;
    result->hasReturning = (parse->returningList != NIL);
    result->hasModifyingCTE = parse->hasModifyingCTE;
    result->canSetTag = parse->canSetTag;
    result->transientPlan = glob->transientPlan;
    result->dependsOnRole = glob->dependsOnRole;
    result->parallelModeNeeded = glob->parallelModeNeeded;
    result->planTree = top_plan;//執(zhí)行計(jì)劃(這是后續(xù)執(zhí)行SQL使用到的最重要的地方)
    result->rtable = glob->finalrtable;
    result->resultRelations = glob->resultRelations;
    result->nonleafResultRelations = glob->nonleafResultRelations;
    result->rootResultRelations = glob->rootResultRelations;
    result->subplans = glob->subplans;
    result->rewindPlanIDs = glob->rewindPlanIDs;
    result->rowMarks = glob->finalrowmarks;
    result->relationOids = glob->relationOids;
    result->invalItems = glob->invalItems;
    result->paramExecTypes = glob->paramExecTypes;
    /* utilityStmt should be null, but we might as well copy it */
    result->utilityStmt = parse->utilityStmt;
    result->stmt_location = parse->stmt_location;
    result->stmt_len = parse->stmt_len;

    result->jitFlags = PGJIT_NONE;
    if (jit_enabled && jit_above_cost >= 0 &&
        top_plan->total_cost > jit_above_cost)
    {
        result->jitFlags |= PGJIT_PERFORM;

        /*
         * Decide how much effort should be put into generating better code.
         */
        if (jit_optimize_above_cost >= 0 &&
            top_plan->total_cost > jit_optimize_above_cost)
            result->jitFlags |= PGJIT_OPT3;
        if (jit_inline_above_cost >= 0 &&
            top_plan->total_cost > jit_inline_above_cost)
            result->jitFlags |= PGJIT_INLINE;

        /*
         * Decide which operations should be JITed.
         */
        if (jit_expressions)
            result->jitFlags |= PGJIT_EXPR;
        if (jit_tuple_deforming)
            result->jitFlags |= PGJIT_DEFORM;
    }

    return result;
} 

pg_plan_queries&pg_plan_query

 /*
  * Generate plans for a list of already-rewritten queries.
  *
  * For normal optimizable statements, invoke the planner.  For utility
  * statements, just make a wrapper PlannedStmt node.
  *
  * The result is a list of PlannedStmt nodes.
  */
 List *
 pg_plan_queries(List *querytrees, int cursorOptions, ParamListInfo boundParams)
 {
     List       *stmt_list = NIL;
     ListCell   *query_list;
 
     foreach(query_list, querytrees)
     {
         Query      *query = lfirst_node(Query, query_list);
         PlannedStmt *stmt;
 
         if (query->commandType == CMD_UTILITY)
         {
             /* Utility commands require no planning. */
             stmt = makeNode(PlannedStmt);
             stmt->commandType = CMD_UTILITY;
             stmt->canSetTag = query->canSetTag;
             stmt->utilityStmt = query->utilityStmt;
             stmt->stmt_location = query->stmt_location;
             stmt->stmt_len = query->stmt_len;
         }
         else
         {
             stmt = pg_plan_query(query, cursorOptions, boundParams);
         }
 
         stmt_list = lappend(stmt_list, stmt);
     }
 
     return stmt_list;
 }

/*
 * Generate a plan for a single already-rewritten query.
 * This is a thin wrapper around planner() and takes the same parameters.
 */
PlannedStmt *
pg_plan_query(Query *querytree, int cursorOptions, ParamListInfo boundParams)
{
    PlannedStmt *plan;

    /* Utility commands have no plans. */
    if (querytree->commandType == CMD_UTILITY)
        return NULL;

    /* Planner must have a snapshot in case it calls user-defined functions. */
    Assert(ActiveSnapshotSet());

    TRACE_POSTGRESQL_QUERY_PLAN_START();

    if (log_planner_stats)
        ResetUsage();

    /* call the optimizer */
    plan = planner(querytree, cursorOptions, boundParams);

    if (log_planner_stats)
        ShowUsage("PLANNER STATISTICS");

#ifdef COPY_PARSE_PLAN_TREES
    /* Optional debugging check: pass plan tree through copyObject() */
    {
        PlannedStmt *new_plan = copyObject(plan);

        /*
         * equal() currently does not have routines to compare Plan nodes, so
         * don't try to test equality here.  Perhaps fix someday?
         */
#ifdef NOT_USED
        /* This checks both copyObject() and the equal() routines... */
        if (!equal(new_plan, plan))
            elog(WARNING, "copyObject() failed to produce an equal plan tree");
        else
#endif
            plan = new_plan;
    }
#endif

#ifdef WRITE_READ_PARSE_PLAN_TREES
    /* Optional debugging check: pass plan tree through outfuncs/readfuncs */
    {
        char       *str;
        PlannedStmt *new_plan;

        str = nodeToString(plan);
        new_plan = stringToNodeWithLocations(str);
        pfree(str);

        /*
         * equal() currently does not have routines to compare Plan nodes, so
         * don't try to test equality here.  Perhaps fix someday?
         */
#ifdef NOT_USED
        /* This checks both outfuncs/readfuncs and the equal() routines... */
        if (!equal(new_plan, plan))
            elog(WARNING, "outfuncs/readfuncs failed to produce an equal plan tree");
        else
#endif
            plan = new_plan;
    }
#endif

    /*
     * Print plan if debugging.
     */
    if (Debug_print_plan)
        elog_node_display(LOG, "plan", plan, Debug_pretty_print);

    TRACE_POSTGRESQL_QUERY_PLAN_DONE();

    return plan;
}

二、參考資料

allpaths.c
PG Document:Query Planning

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI