溫馨提示×

溫馨提示×

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

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

PostgreSQL如何為append relation構(gòu)建訪問路徑

發(fā)布時間:2021-11-10 14:59:14 來源:億速云 閱讀:105 作者:小新 欄目:關(guān)系型數(shù)據(jù)庫

這篇文章給大家分享的是有關(guān)PostgreSQL如何為append relation構(gòu)建訪問路徑的內(nèi)容。小編覺得挺實用的,因此分享給大家做個參考,一起跟隨小編過來看看吧。

一、數(shù)據(jù)結(jié)構(gòu)

AppendRelInfo
當(dāng)我們將可繼承表(分區(qū)表)或UNION-ALL子查詢展開為“追加關(guān)系”(本質(zhì)上是子RTE的鏈表)時,為每個子RTE構(gòu)建一個AppendRelInfo。

/*
 * Append-relation info.
 * Append-relation信息.
 * 
 * When we expand an inheritable table or a UNION-ALL subselect into an
 * "append relation" (essentially, a list of child RTEs), we build an
 * AppendRelInfo for each child RTE.  The list of AppendRelInfos indicates
 * which child RTEs must be included when expanding the parent, and each node
 * carries information needed to translate Vars referencing the parent into
 * Vars referencing that child.
 * 當(dāng)我們將可繼承表(分區(qū)表)或UNION-ALL子查詢展開為“追加關(guān)系”(本質(zhì)上是子RTE的鏈表)時,
 *   為每個子RTE構(gòu)建一個AppendRelInfo。
 * AppendRelInfos鏈表指示在展開父節(jié)點時必須包含哪些子rte,
 *   每個節(jié)點具有將引用父節(jié)點的Vars轉(zhuǎn)換為引用該子節(jié)點的Vars所需的所有信息。
 * 
 * These structs are kept in the PlannerInfo node's append_rel_list.
 * Note that we just throw all the structs into one list, and scan the
 * whole list when desiring to expand any one parent.  We could have used
 * a more complex data structure (eg, one list per parent), but this would
 * be harder to update during operations such as pulling up subqueries,
 * and not really any easier to scan.  Considering that typical queries
 * will not have many different append parents, it doesn't seem worthwhile
 * to complicate things.
 * 這些結(jié)構(gòu)體保存在PlannerInfo節(jié)點的append_rel_list中。
 * 注意,只是將所有的結(jié)構(gòu)體放入一個鏈表中,并在希望展開任何父類時掃描整個鏈表。
 * 本可以使用更復(fù)雜的數(shù)據(jù)結(jié)構(gòu)(例如,每個父節(jié)點一個列表),
 *   但是在提取子查詢之類的操作中更新它會更困難,
 *   而且實際上也不會更容易掃描。
 * 考慮到典型的查詢不會有很多不同的附加項,因此似乎不值得將事情復(fù)雜化。
 * 
 * Note: after completion of the planner prep phase, any given RTE is an
 * append parent having entries in append_rel_list if and only if its
 * "inh" flag is set.  We clear "inh" for plain tables that turn out not
 * to have inheritance children, and (in an abuse of the original meaning
 * of the flag) we set "inh" for subquery RTEs that turn out to be
 * flattenable UNION ALL queries.  This lets us avoid useless searches
 * of append_rel_list.
 * 注意:計劃準(zhǔn)備階段完成后,
 *   當(dāng)且僅當(dāng)它的“inh”標(biāo)志已設(shè)置時,給定的RTE是一個append parent在append_rel_list中的一個條目。
 * 我們?yōu)闆]有child的平面表清除“inh”標(biāo)記,
 *   同時(有濫用標(biāo)記的嫌疑)為UNION ALL查詢中的子查詢RTEs設(shè)置“inh”標(biāo)記。
 * 這樣可以避免對append_rel_list進行無用的搜索。
 * 
 * Note: the data structure assumes that append-rel members are single
 * baserels.  This is OK for inheritance, but it prevents us from pulling
 * up a UNION ALL member subquery if it contains a join.  While that could
 * be fixed with a more complex data structure, at present there's not much
 * point because no improvement in the plan could result.
 * 注意:數(shù)據(jù)結(jié)構(gòu)假定附加的rel成員是獨立的baserels。
 * 這對于繼承來說是可以的,但是如果UNION ALL member子查詢包含一個join,
 *   那么它將阻止我們提取UNION ALL member子查詢。
 * 雖然可以用更復(fù)雜的數(shù)據(jù)結(jié)構(gòu)解決這個問題,但目前沒有太大意義,因為該計劃可能不會有任何改進。
 */

typedef struct AppendRelInfo
{
    NodeTag     type;

    /*
     * These fields uniquely identify this append relationship.  There can be
     * (in fact, always should be) multiple AppendRelInfos for the same
     * parent_relid, but never more than one per child_relid, since a given
     * RTE cannot be a child of more than one append parent.
     * 這些字段惟一地標(biāo)識這個append relationship。
     * 對于同一個parent_relid可以有(實際上應(yīng)該總是)多個AppendRelInfos,
     *   但是每個child_relid不能有多個AppendRelInfos,
     *   因為給定的RTE不能是多個append parent的子節(jié)點。
     */
    Index       parent_relid;   /* parent rel的RT索引;RT index of append parent rel */
    Index       child_relid;    /* child rel的RT索引;RT index of append child rel */

    /*
     * For an inheritance appendrel, the parent and child are both regular
     * relations, and we store their rowtype OIDs here for use in translating
     * whole-row Vars.  For a UNION-ALL appendrel, the parent and child are
     * both subqueries with no named rowtype, and we store InvalidOid here.
     * 對于繼承appendrel,父類和子類都是普通關(guān)系,
     *   我們將它們的rowtype OIDs存儲在這里,用于轉(zhuǎn)換whole-row Vars。
     * 對于UNION-ALL appendrel,父查詢和子查詢都是沒有指定行類型的子查詢,
     * 我們在這里存儲InvalidOid。
     */
    Oid         parent_reltype; /* OID of parent's composite type */
    Oid         child_reltype;  /* OID of child's composite type */

    /*
     * The N'th element of this list is a Var or expression representing the
     * child column corresponding to the N'th column of the parent. This is
     * used to translate Vars referencing the parent rel into references to
     * the child.  A list element is NULL if it corresponds to a dropped
     * column of the parent (this is only possible for inheritance cases, not
     * UNION ALL).  The list elements are always simple Vars for inheritance
     * cases, but can be arbitrary expressions in UNION ALL cases.
     * 這個列表的第N個元素是一個Var或表達式,表示與父元素的第N列對應(yīng)的子列。
     * 這用于將引用parent rel的Vars轉(zhuǎn)換為對子rel的引用。
     * 如果鏈表元素與父元素的已刪除列相對應(yīng),則該元素為NULL
     *   (這只適用于繼承情況,而不是UNION ALL)。
     * 對于繼承情況,鏈表元素總是簡單的變量,但是可以是UNION ALL情況下的任意表達式。
     *
     * Notice we only store entries for user columns (attno > 0).  Whole-row
     * Vars are special-cased, and system columns (attno < 0) need no special
     * translation since their attnos are the same for all tables.
     * 注意,我們只存儲用戶列的條目(attno > 0)。
     * Whole-row Vars是大小寫敏感的,系統(tǒng)列(attno < 0)不需要特別的轉(zhuǎn)換,
     *   因為它們的attno對所有表都是相同的。
     *
     * Caution: the Vars have varlevelsup = 0.  Be careful to adjust as needed
     * when copying into a subquery.
     * 注意:Vars的varlevelsup = 0。
     * 在將數(shù)據(jù)復(fù)制到子查詢時,要注意根據(jù)需要進行調(diào)整。
     */
    //child's Vars中的表達式
    List       *translated_vars;    /* Expressions in the child's Vars */

    /*
     * We store the parent table's OID here for inheritance, or InvalidOid for
     * UNION ALL.  This is only needed to help in generating error messages if
     * an attempt is made to reference a dropped parent column.
     * 我們將父表的OID存儲在這里用于繼承,
     *   如為UNION ALL,則這里存儲的是InvalidOid。
     * 只有在試圖引用已刪除的父列時,才需要這樣做來幫助生成錯誤消息。
     */
    Oid         parent_reloid;  /* OID of parent relation */
} AppendRelInfo;

RelOptInfo
規(guī)劃器/優(yōu)化器使用的關(guān)系信息結(jié)構(gòu)體
參見PostgreSQL 源碼解讀(99)- 分區(qū)表#5(數(shù)據(jù)查詢路由#2-RelOptInfo數(shù)據(jù)結(jié)構(gòu))

二、源碼解讀

set_rel_pathlist函數(shù)為基礎(chǔ)關(guān)系構(gòu)建訪問路徑.

//是否DUMMY訪問路徑
#define IS_DUMMY_PATH(p) \
    (IsA((p), AppendPath) && ((AppendPath *) (p))->subpaths == NIL)

/* A relation that's been proven empty will have one path that is dummy */
//已被證明為空的關(guān)系會含有一個虛擬dummy的訪問路徑
#define IS_DUMMY_REL(r) \
    ((r)->cheapest_total_path != NULL && \
     IS_DUMMY_PATH((r)->cheapest_total_path))


/*
 * set_rel_pathlist
 *    Build access paths for a base relation
 *    為基礎(chǔ)關(guān)系構(gòu)建訪問路徑
 */
static void
set_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
                 Index rti, RangeTblEntry *rte)
{
    if (IS_DUMMY_REL(rel))
    {
        /* We already proved the relation empty, so nothing more to do */
        //不需要做任何處理
    }
    else if (rte->inh)
    {
        /* It's an "append relation", process accordingly */
        //append relation,調(diào)用set_append_rel_pathlist處理
        set_append_rel_pathlist(root, rel, rti, rte);
    }
    else
    {//其他類型的關(guān)系
        switch (rel->rtekind)
        {
            case RTE_RELATION://基礎(chǔ)關(guān)系
                if (rte->relkind == RELKIND_FOREIGN_TABLE)
                {
                    /* Foreign table */
                    //外部表
                    set_foreign_pathlist(root, rel, rte);
                }
                else if (rte->tablesample != NULL)
                {
                    /* Sampled relation */
                    //數(shù)據(jù)表采樣
                    set_tablesample_rel_pathlist(root, rel, rte);
                }
                else
                {
                    /* Plain relation */
                    //常規(guī)關(guān)系
                    set_plain_rel_pathlist(root, rel, rte);
                }
                break;
            case RTE_SUBQUERY:
                /* Subquery --- fully handled during set_rel_size */
                //子查詢
                break;
            case RTE_FUNCTION:
                /* RangeFunction */
                set_function_pathlist(root, rel, rte);
                break;
            case RTE_TABLEFUNC:
                /* Table Function */
                set_tablefunc_pathlist(root, rel, rte);
                break;
            case RTE_VALUES:
                /* Values list */
                set_values_pathlist(root, rel, rte);
                break;
            case RTE_CTE:
                /* CTE reference --- fully handled during set_rel_size */
                break;
            case RTE_NAMEDTUPLESTORE:
                /* tuplestore reference --- fully handled during set_rel_size */
                break;
            default:
                elog(ERROR, "unexpected rtekind: %d", (int) rel->rtekind);
                break;
        }
    }

    /*
     * If this is a baserel, we should normally consider gathering any partial
     * paths we may have created for it.
     * 如為基礎(chǔ)關(guān)系,通常來說需要考慮聚集gathering所有的部分路徑(先前已構(gòu)建)
     *
     * However, if this is an inheritance child, skip it.  Otherwise, we could
     * end up with a very large number of gather nodes, each trying to grab
     * its own pool of workers.  Instead, we'll consider gathering partial
     * paths for the parent appendrel.
     * 但是,如果這是一個繼承關(guān)系中的子表,忽略它.
     * 否則的話,我們最好可能會有很大量的Gather節(jié)點,每一個都嘗試grab它自己worker的輸出.
     * 相反我們的處理是為父append relation生成gathering部分訪問路徑.
     *
     * Also, if this is the topmost scan/join rel (that is, the only baserel),
     * we postpone this until the final scan/join targelist is available (see
     * grouping_planner).
     * 另外,如果這是最高層的scan/join關(guān)系(基礎(chǔ)關(guān)系),
     *   在完成了最后的scan/join投影列處理后才進行相應(yīng)的處理.
     */
    if (rel->reloptkind == RELOPT_BASEREL &&
        bms_membership(root->all_baserels) != BMS_SINGLETON)
        generate_gather_paths(root, rel, false);

    /*
     * Allow a plugin to editorialize on the set of Paths for this base
     * relation.  It could add new paths (such as CustomPaths) by calling
     * add_path(), or delete or modify paths added by the core code.
     * 調(diào)用鉤子函數(shù).
     * 插件可以通過調(diào)用add_path添加新的訪問路徑(比如CustomPaths等),
     *   或者刪除/修改核心代碼生成的訪問路徑.
     */
    if (set_rel_pathlist_hook)
        (*set_rel_pathlist_hook) (root, rel, rti, rte);

    /* Now find the cheapest of the paths for this rel */
    //為rel找到成本最低的訪問路徑
    set_cheapest(rel);

#ifdef OPTIMIZER_DEBUG
    debug_print_rel(root, rel);
#endif
}


 /*
 * set_append_rel_pathlist
 *    Build access paths for an "append relation"
 *    為append relation構(gòu)建訪問路徑
 */
static void
set_append_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
                        Index rti, RangeTblEntry *rte)
{
    int         parentRTindex = rti;
    List       *live_childrels = NIL;
    ListCell   *l;

    /*
     * Generate access paths for each member relation, and remember the
     * non-dummy children.
     * 為每個成員關(guān)系生成訪問路徑,并"記住"非虛擬子節(jié)點。
     */
    foreach(l, root->append_rel_list)//遍歷鏈表
    {
        AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(l);//獲取AppendRelInfo
        int         childRTindex;
        RangeTblEntry *childRTE;
        RelOptInfo *childrel;

        /* append_rel_list contains all append rels; ignore others */
        //append_rel_list含有所有的append relations,忽略其他的rels
        if (appinfo->parent_relid != parentRTindex)
            continue;

        /* Re-locate the child RTE and RelOptInfo */
        //重新定位子RTE和RelOptInfo
        childRTindex = appinfo->child_relid;
        childRTE = root->simple_rte_array[childRTindex];
        childrel = root->simple_rel_array[childRTindex];

        /*
         * If set_append_rel_size() decided the parent appendrel was
         * parallel-unsafe at some point after visiting this child rel, we
         * need to propagate the unsafety marking down to the child, so that
         * we don't generate useless partial paths for it.
         * 如果set_append_rel_size()函數(shù)在訪問了子關(guān)系后,
         *   在某些點上斷定父append relation是非并行安全的,
         *   我們需要分發(fā)不安全的標(biāo)記到子關(guān)系中,以免產(chǎn)生無用的部分訪問路徑.
         */
        if (!rel->consider_parallel)
            childrel->consider_parallel = false;

        /*
         * Compute the child's access paths.
         * "計算"子關(guān)系的訪問路徑
         */
        set_rel_pathlist(root, childrel, childRTindex, childRTE);

        /*
         * If child is dummy, ignore it.
         * 如為虛擬關(guān)系,忽略之
         */
        if (IS_DUMMY_REL(childrel))
            continue;

        /* Bubble up childrel's partitioned children. */
        //
        if (rel->part_scheme)
            rel->partitioned_child_rels =
                list_concat(rel->partitioned_child_rels,
                            list_copy(childrel->partitioned_child_rels));

        /*
         * Child is live, so add it to the live_childrels list for use below.
         * 添加到live_childrels鏈表中
         */
        live_childrels = lappend(live_childrels, childrel);
    }

    /* Add paths to the append relation. */
    //添加訪問路徑到append relation中
    add_paths_to_append_rel(root, rel, live_childrels);
}


/*
 * add_paths_to_append_rel
 *      Generate paths for the given append relation given the set of non-dummy
 *      child rels.
 *      基于非虛擬子關(guān)系集合為給定的append relation生成訪問路徑
 *
 * The function collects all parameterizations and orderings supported by the
 * non-dummy children. For every such parameterization or ordering, it creates
 * an append path collecting one path from each non-dummy child with given
 * parameterization or ordering. Similarly it collects partial paths from
 * non-dummy children to create partial append paths.
 * 該函數(shù)收集所有的非虛擬關(guān)系支持的參數(shù)化和排序訪問路徑.
 * 對于每一個參數(shù)化或排序的訪問路徑,它創(chuàng)建一個附加路徑,
 *   從每個具有給定參數(shù)化或排序的非虛擬子節(jié)點收集相關(guān)信息。
 * 類似的,從非虛擬子關(guān)系中收集部分訪問路徑用以創(chuàng)建部分append路徑.
 */
void
add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel,
                        List *live_childrels)
{
    List       *subpaths = NIL;
    bool        subpaths_valid = true;
    List       *partial_subpaths = NIL;
    List       *pa_partial_subpaths = NIL;
    List       *pa_nonpartial_subpaths = NIL;
    bool        partial_subpaths_valid = true;
    bool        pa_subpaths_valid;
    List       *all_child_pathkeys = NIL;
    List       *all_child_outers = NIL;
    ListCell   *l;
    List       *partitioned_rels = NIL;
    double      partial_rows = -1;

    /* If appropriate, consider parallel append */
    //如可以,考慮并行append
    pa_subpaths_valid = enable_parallel_append && rel->consider_parallel;

    /*
     * AppendPath generated for partitioned tables must record the RT indexes
     * of partitioned tables that are direct or indirect children of this
     * Append rel.
     * 為分區(qū)表生成的AppendPath必須記錄屬于該分區(qū)表(Append Rel)的直接或間接子關(guān)系的RT索引
     *
     * AppendPath may be for a sub-query RTE (UNION ALL), in which case, 'rel'
     * itself does not represent a partitioned relation, but the child sub-
     * queries may contain references to partitioned relations.  The loop
     * below will look for such children and collect them in a list to be
     * passed to the path creation function.  (This assumes that we don't need
     * to look through multiple levels of subquery RTEs; if we ever do, we
     * could consider stuffing the list we generate here into sub-query RTE's
     * RelOptInfo, just like we do for partitioned rels, which would be used
     * when populating our parent rel with paths.  For the present, that
     * appears to be unnecessary.)
     * AppendPath可能是子查詢RTE(UNION ALL),在這種情況下,rel自身并不代表分區(qū)表,
     *   但child sub-queries可能含有分區(qū)表的依賴.
     * 以下的循環(huán)會尋找這樣的子關(guān)系,存儲在鏈表中,并作為參數(shù)傳給訪問路徑創(chuàng)建函數(shù).
     * (這是假設(shè)我們不需要遍歷多個層次的子查詢RTEs,如果我們曾經(jīng)這樣做了,
     *  這好比分區(qū)表的做法,可以考慮把生成的鏈表放到子查詢的RTE's RelOptInfo結(jié)構(gòu)體中,
     *  用于使用訪問路徑填充父關(guān)系.不過目前來說,這樣做是不需要的.)
     */
    if (rel->part_scheme != NULL)
    {
        if (IS_SIMPLE_REL(rel))
            partitioned_rels = list_make1(rel->partitioned_child_rels);
        else if (IS_JOIN_REL(rel))
        {
            int         relid = -1;
            List       *partrels = NIL;

            /*
             * For a partitioned joinrel, concatenate the component rels'
             * partitioned_child_rels lists.
             * 對于分區(qū)連接rel,連接rels的partitioned_child_rels鏈表
             *
             */
            while ((relid = bms_next_member(rel->relids, relid)) >= 0)
            {
                RelOptInfo *component;

                Assert(relid >= 1 && relid < root->simple_rel_array_size);
                component = root->simple_rel_array[relid];
                Assert(component->part_scheme != NULL);
                Assert(list_length(component->partitioned_child_rels) >= 1);
                partrels =
                    list_concat(partrels,
                                list_copy(component->partitioned_child_rels));
            }

            partitioned_rels = list_make1(partrels);
        }

        Assert(list_length(partitioned_rels) >= 1);
    }

    /*
     * For every non-dummy child, remember the cheapest path.  Also, identify
     * all pathkeys (orderings) and parameterizations (required_outer sets)
     * available for the non-dummy member relations.
     * 對于每一個非虛擬子關(guān)系,記錄成本最低的訪問路徑.
     * 同時,為每一個非虛擬成員關(guān)系標(biāo)識所有的路徑鍵(排序)和可用的參數(shù)化信息(required_outer集合)
     */
    foreach(l, live_childrels)//遍歷
    {
        RelOptInfo *childrel = lfirst(l);
        ListCell   *lcp;
        Path       *cheapest_partial_path = NULL;

        /*
         * For UNION ALLs with non-empty partitioned_child_rels, accumulate
         * the Lists of child relations.
         * 對于包含非空的partitioned_child_rels的UNION ALLs操作,
         *   累積子關(guān)系鏈表
         */
        if (rel->rtekind == RTE_SUBQUERY && childrel->partitioned_child_rels != NIL)
            partitioned_rels = lappend(partitioned_rels,
                                       childrel->partitioned_child_rels);

        /*
         * If child has an unparameterized cheapest-total path, add that to
         * the unparameterized Append path we are constructing for the parent.
         * If not, there's no workable unparameterized path.
         * 如果子關(guān)系存在非參數(shù)化的總成本最低的訪問路徑,
         *   添加此路徑到我們?yōu)楦戈P(guān)系構(gòu)建的非參數(shù)化的Append訪問路徑中.
         *
         * With partitionwise aggregates, the child rel's pathlist may be
         * empty, so don't assume that a path exists here.
         * 使用partitionwise聚合,子關(guān)系的訪問路徑鏈表可能是空的,不能假設(shè)其中存在訪問路徑
         */
        if (childrel->pathlist != NIL &&
            childrel->cheapest_total_path->param_info == NULL)
            accumulate_append_subpath(childrel->cheapest_total_path,
                                      &subpaths, NULL);
        else
            subpaths_valid = false;

        /* Same idea, but for a partial plan. */
        //同樣的思路,處理并行處理中的部分計劃
        if (childrel->partial_pathlist != NIL)
        {
            cheapest_partial_path = linitial(childrel->partial_pathlist);
            accumulate_append_subpath(cheapest_partial_path,
                                      &partial_subpaths, NULL);
        }
        else
            partial_subpaths_valid = false;

        /*
         * Same idea, but for a parallel append mixing partial and non-partial
         * paths.
         * 同樣的,處理并行append混合并行/非并行訪問路徑
         */
        if (pa_subpaths_valid)
        {
            Path       *nppath = NULL;

            nppath =
                get_cheapest_parallel_safe_total_inner(childrel->pathlist);

            if (cheapest_partial_path == NULL && nppath == NULL)
            {
                /* Neither a partial nor a parallel-safe path?  Forget it. */
                //不是部分路徑,也不是并行安全的路徑,跳過
                pa_subpaths_valid = false;
            }
            else if (nppath == NULL ||
                     (cheapest_partial_path != NULL &&
                      cheapest_partial_path->total_cost < nppath->total_cost))
            {
                /* Partial path is cheaper or the only option. */
                //部分路徑成本更低或者是唯一的選項
                Assert(cheapest_partial_path != NULL);
                accumulate_append_subpath(cheapest_partial_path,
                                          &pa_partial_subpaths,
                                          &pa_nonpartial_subpaths);

            }
            else
            {
                /*
                 * Either we've got only a non-partial path, or we think that
                 * a single backend can execute the best non-partial path
                 * faster than all the parallel backends working together can
                 * execute the best partial path.
                 * 這時候,要么得到了一個非并行訪問路徑,或者我們認為一個單獨的后臺進程
                 *   執(zhí)行最好的非并行訪問路徑會比索引的并行進程一起執(zhí)行最好的部分路徑還要好.
                 *
                 * It might make sense to be more aggressive here.  Even if
                 * the best non-partial path is more expensive than the best
                 * partial path, it could still be better to choose the
                 * non-partial path if there are several such paths that can
                 * be given to different workers.  For now, we don't try to
                 * figure that out.
                 * 在這里,采取更積極的態(tài)度是有道理的.
                 * 甚至最好的非部分路徑比最好的并行部分路徑成本更高,仍然需要選擇非并行路徑,
                 *   如果多個這樣的路徑可能會分派到不同的worker上.
                 * 現(xiàn)在不需要指出這一點.
                 */
                accumulate_append_subpath(nppath,
                                          &pa_nonpartial_subpaths,
                                          NULL);
            }
        }

        /*
         * Collect lists of all the available path orderings and
         * parameterizations for all the children.  We use these as a
         * heuristic to indicate which sort orderings and parameterizations we
         * should build Append and MergeAppend paths for.
         * 收集子關(guān)系所有可用的排序和參數(shù)化路徑鏈表.
         * 我們采用啟發(fā)式的規(guī)則判斷Append和MergeAppend訪問路徑使用哪個排序和參數(shù)化信息
         */
        foreach(lcp, childrel->pathlist)
        {
            Path       *childpath = (Path *) lfirst(lcp);
            List       *childkeys = childpath->pathkeys;
            Relids      childouter = PATH_REQ_OUTER(childpath);

            /* Unsorted paths don't contribute to pathkey list */
            //未排序的訪問路徑,不需要分發(fā)到路徑鍵鏈表中
            if (childkeys != NIL)
            {
                ListCell   *lpk;
                bool        found = false;

                /* Have we already seen this ordering? */
                foreach(lpk, all_child_pathkeys)
                {
                    List       *existing_pathkeys = (List *) lfirst(lpk);

                    if (compare_pathkeys(existing_pathkeys,
                                         childkeys) == PATHKEYS_EQUAL)
                    {
                        found = true;
                        break;
                    }
                }
                if (!found)
                {
                    /* No, so add it to all_child_pathkeys */
                    all_child_pathkeys = lappend(all_child_pathkeys,
                                                 childkeys);
                }
            }

            /* Unparameterized paths don't contribute to param-set list */
            //非參數(shù)訪問路徑無需分發(fā)到參數(shù)化集合鏈表中
            if (childouter)
            {
                ListCell   *lco;
                bool        found = false;

                /* Have we already seen this param set? */
                foreach(lco, all_child_outers)
                {
                    Relids      existing_outers = (Relids) lfirst(lco);

                    if (bms_equal(existing_outers, childouter))
                    {
                        found = true;
                        break;
                    }
                }
                if (!found)
                {
                    /* No, so add it to all_child_outers */
                    all_child_outers = lappend(all_child_outers,
                                               childouter);
                }
            }
        }
    }

    /*
     * If we found unparameterized paths for all children, build an unordered,
     * unparameterized Append path for the rel.  (Note: this is correct even
     * if we have zero or one live subpath due to constraint exclusion.)
     * 如存在子關(guān)系的非參數(shù)化訪問路徑,構(gòu)建未排序/未參數(shù)化的Append訪問路徑.
     * (注意:如果存在約束排除,我們只剩下有0或1個存活的subpath,這樣的處理也說OK的)
     */
    if (subpaths_valid)
        add_path(rel, (Path *) create_append_path(root, rel, subpaths, NIL,
                                                  NULL, 0, false,
                                                  partitioned_rels, -1));

    /*
     * Consider an append of unordered, unparameterized partial paths.  Make
     * it parallel-aware if possible.
     * 嘗試未排序/未參數(shù)化的部分Append訪問路徑.
     * 如可能,構(gòu)建parallel-aware訪問路徑.
     */
    if (partial_subpaths_valid)
    {
        AppendPath *appendpath;
        ListCell   *lc;
        int         parallel_workers = 0;

        /* Find the highest number of workers requested for any subpath. */
        //為子訪問路徑尋找最多數(shù)量的wokers
        foreach(lc, partial_subpaths)
        {
            Path       *path = lfirst(lc);

            parallel_workers = Max(parallel_workers, path->parallel_workers);
        }
        Assert(parallel_workers > 0);

        /*
         * If the use of parallel append is permitted, always request at least
         * log2(# of children) workers.  We assume it can be useful to have
         * extra workers in this case because they will be spread out across
         * the children.  The precise formula is just a guess, but we don't
         * want to end up with a radically different answer for a table with N
         * partitions vs. an unpartitioned table with the same data, so the
         * use of some kind of log-scaling here seems to make some sense.
         * 如允許使用并行append,那么至少需要log2(子關(guān)系個數(shù))個workers.
         * 經(jīng)過擴展后,假定可以使用額外的workers.
         * 并不存在精確的計算公式,目前只是猜測而已,但是對于有相同數(shù)據(jù)的N個分區(qū)的分區(qū)表和非分區(qū)表來說,
         *   答案是不一樣的,因此在這里使用對數(shù)的計算方法是OK的.
         */
        if (enable_parallel_append)
        {
            parallel_workers = Max(parallel_workers,
                                   fls(list_length(live_childrels)));//上限值
            parallel_workers = Min(parallel_workers,
                                   max_parallel_workers_per_gather);//下限值
        }
        Assert(parallel_workers > 0);

        /* Generate a partial append path. */
        //生成并行部分append訪問路徑
        appendpath = create_append_path(root, rel, NIL, partial_subpaths,
                                        NULL, parallel_workers,
                                        enable_parallel_append,
                                        partitioned_rels, -1);

        /*
         * Make sure any subsequent partial paths use the same row count
         * estimate.
         * 確保所有的子部分路徑使用相同的行數(shù)估算
         */
        partial_rows = appendpath->path.rows;

        /* Add the path. */
        //添加路徑
        add_partial_path(rel, (Path *) appendpath);
    }

    /*
     * Consider a parallel-aware append using a mix of partial and non-partial
     * paths.  (This only makes sense if there's at least one child which has
     * a non-partial path that is substantially cheaper than any partial path;
     * otherwise, we should use the append path added in the previous step.)
     * 使用混合的部分和非部分并行的append.
     */
    if (pa_subpaths_valid && pa_nonpartial_subpaths != NIL)
    {
        AppendPath *appendpath;
        ListCell   *lc;
        int         parallel_workers = 0;

        /*
         * Find the highest number of workers requested for any partial
         * subpath.
         */
        foreach(lc, pa_partial_subpaths)
        {
            Path       *path = lfirst(lc);

            parallel_workers = Max(parallel_workers, path->parallel_workers);
        }

        /*
         * Same formula here as above.  It's even more important in this
         * instance because the non-partial paths won't contribute anything to
         * the planned number of parallel workers.
         */
        parallel_workers = Max(parallel_workers,
                               fls(list_length(live_childrels)));
        parallel_workers = Min(parallel_workers,
                               max_parallel_workers_per_gather);
        Assert(parallel_workers > 0);

        appendpath = create_append_path(root, rel, pa_nonpartial_subpaths,
                                        pa_partial_subpaths,
                                        NULL, parallel_workers, true,
                                        partitioned_rels, partial_rows);
        add_partial_path(rel, (Path *) appendpath);
    }

    /*
     * Also build unparameterized MergeAppend paths based on the collected
     * list of child pathkeys.
     * 基于收集的子路徑鍵,構(gòu)建非參數(shù)化的MergeAppend訪問路徑
     */
    if (subpaths_valid)
        generate_mergeappend_paths(root, rel, live_childrels,
                                   all_child_pathkeys,
                                   partitioned_rels);

    /*
     * Build Append paths for each parameterization seen among the child rels.
     * (This may look pretty expensive, but in most cases of practical
     * interest, the child rels will expose mostly the same parameterizations,
     * so that not that many cases actually get considered here.)
     * 為每一個參數(shù)化的子關(guān)系構(gòu)建Append訪問路徑.
     * (看起來成本客觀,但在大多數(shù)情況下,子關(guān)系使用同樣的參數(shù)化信息,因此實際上并不是經(jīng)常發(fā)現(xiàn))
     *
     * The Append node itself cannot enforce quals, so all qual checking must
     * be done in the child paths.  This means that to have a parameterized
     * Append path, we must have the exact same parameterization for each
     * child path; otherwise some children might be failing to check the
     * moved-down quals.  To make them match up, we can try to increase the
     * parameterization of lesser-parameterized paths.
     */
    foreach(l, all_child_outers)
    {
        Relids      required_outer = (Relids) lfirst(l);
        ListCell   *lcr;

        /* Select the child paths for an Append with this parameterization */
        subpaths = NIL;
        subpaths_valid = true;
        foreach(lcr, live_childrels)
        {
            RelOptInfo *childrel = (RelOptInfo *) lfirst(lcr);
            Path       *subpath;

            if (childrel->pathlist == NIL)
            {
                /* failed to make a suitable path for this child */
                subpaths_valid = false;
                break;
            }

            subpath = get_cheapest_parameterized_child_path(root,
                                                            childrel,
                                                            required_outer);
            if (subpath == NULL)
            {
                /* failed to make a suitable path for this child */
                subpaths_valid = false;
                break;
            }
            accumulate_append_subpath(subpath, &subpaths, NULL);
        }

        if (subpaths_valid)
            add_path(rel, (Path *)
                     create_append_path(root, rel, subpaths, NIL,
                                        required_outer, 0, false,
                                        partitioned_rels, -1));
    }
}

三、跟蹤分析

測試腳本如下

testdb=# explain verbose select * from t_hash_partition where c1 = 1 OR c1 = 2;
                                     QUERY PLAN                                      
-------------------------------------------------------------------------------------
 Append  (cost=0.00..30.53 rows=6 width=200)
   ->  Seq Scan on public.t_hash_partition_1  (cost=0.00..15.25 rows=3 width=200)
         Output: t_hash_partition_1.c1, t_hash_partition_1.c2, t_hash_partition_1.c3
         Filter: ((t_hash_partition_1.c1 = 1) OR (t_hash_partition_1.c1 = 2))
   ->  Seq Scan on public.t_hash_partition_3  (cost=0.00..15.25 rows=3 width=200)
         Output: t_hash_partition_3.c1, t_hash_partition_3.c2, t_hash_partition_3.c3
         Filter: ((t_hash_partition_3.c1 = 1) OR (t_hash_partition_3.c1 = 2))
(7 rows)

啟動gdb,設(shè)置斷點

(gdb) b set_rel_pathlist
Breakpoint 1 at 0x796823: file allpaths.c, line 425.
(gdb) c
Continuing.

Breakpoint 1, set_rel_pathlist (root=0x1f1e400, rel=0x1efaba0, rti=1, rte=0x1efa3d0) at allpaths.c:425
425     if (IS_DUMMY_REL(rel))
(gdb)

通過rte->inh判斷是否分區(qū)表或者UNION ALL

(gdb) p rte->inh
$1 = true
(gdb)

進入set_append_rel_pathlist函數(shù)

(gdb) n
429     else if (rte->inh)
(gdb) 
432         set_append_rel_pathlist(root, rel, rti, rte);
(gdb) step
set_append_rel_pathlist (root=0x1f1e400, rel=0x1efaba0, rti=1, rte=0x1efa3d0) at allpaths.c:1296
1296        int         parentRTindex = rti;

遍歷子關(guān)系

(gdb) n
1297        List       *live_childrels = NIL;
(gdb) 
1304        foreach(l, root->append_rel_list)
(gdb) 
(gdb) p *root->append_rel_list
$2 = {type = T_List, length = 6, head = 0x1fc1f98, tail = 0x1fc2ae8}

獲取AppendRelInfo,判斷父關(guān)系是否正在處理的父關(guān)系

(gdb) n
1306            AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(l);
(gdb) 
1312            if (appinfo->parent_relid != parentRTindex)
(gdb) p *appinfo
$3 = {type = T_AppendRelInfo, parent_relid = 1, child_relid = 3, parent_reltype = 16988, child_reltype = 16991, 
  translated_vars = 0x1fc1e60, parent_reloid = 16986}
(gdb)

獲取子關(guān)系的相關(guān)信息,遞歸調(diào)用set_rel_pathlist

(gdb) n
1316            childRTindex = appinfo->child_relid;
(gdb) 
1317            childRTE = root->simple_rte_array[childRTindex];
(gdb) 
1318            childrel = root->simple_rel_array[childRTindex];
(gdb) 
1326            if (!rel->consider_parallel)
(gdb) 
1332            set_rel_pathlist(root, childrel, childRTindex, childRTE);
(gdb) 
(gdb) 

Breakpoint 1, set_rel_pathlist (root=0x1f1e400, rel=0x1f1c8a0, rti=3, rte=0x1efa658) at allpaths.c:425
425     if (IS_DUMMY_REL(rel))
(gdb) finish
Run till exit from #0  set_rel_pathlist (root=0x1f1e400, rel=0x1f1c8a0, rti=3, rte=0x1efa658) at allpaths.c:425
set_append_rel_pathlist (root=0x1f1e400, rel=0x1efaba0, rti=1, rte=0x1efa3d0) at allpaths.c:1337

如為虛擬關(guān)系,則忽略之

1337            if (IS_DUMMY_REL(childrel))

該子關(guān)系不是虛擬關(guān)系,繼續(xù)處理,加入到rel->partitioned_child_rels和live_childrels鏈表中

(gdb) n
1341            if (rel->part_scheme)
(gdb) 
1344                                list_copy(childrel->partitioned_child_rels));
(gdb) 
1343                    list_concat(rel->partitioned_child_rels,
(gdb) 
1342                rel->partitioned_child_rels =
(gdb) 
1349            live_childrels = lappend(live_childrels, childrel);
(gdb) p *rel->partitioned_child_rels
$4 = {type = T_IntList, length = 1, head = 0x1fc4d78, tail = 0x1fc4d78}
(gdb) p rel->partitioned_child_rels->head->data.int_value
$6 = 1
(gdb) 
(gdb) n
1304        foreach(l, root->append_rel_list)
(gdb) p live_childrels
$7 = (List *) 0x1fd0a60
(gdb) p *live_childrels
$8 = {type = T_List, length = 1, head = 0x1fd0a38, tail = 0x1fd0a38}
(gdb) p *(Node *)live_childrels->head->data.ptr_value
$9 = {type = T_RelOptInfo}
(gdb) p *(RelOptInfo *)live_childrels->head->data.ptr_value
$10 = {type = T_RelOptInfo, reloptkind = RELOPT_OTHER_MEMBER_REL, relids = 0x1fc3590, rows = 3, consider_startup = false, 
  consider_param_startup = false, consider_parallel = true, reltarget = 0x1fc35b0, pathlist = 0x1fd0940, ppilist = 0x0, 
  partial_pathlist = 0x1fd09a0, cheapest_startup_path = 0x1fc44f8, cheapest_total_path = 0x1fc44f8, 
  cheapest_unique_path = 0x0, cheapest_parameterized_paths = 0x1fd0a00, direct_lateral_relids = 0x0, lateral_relids = 0x0, 
  relid = 3, reltablespace = 0, rtekind = RTE_RELATION, min_attr = -7, max_attr = 3, attr_needed = 0x1fc2e38, 
  attr_widths = 0x1fc3628, lateral_vars = 0x0, lateral_referencers = 0x0, indexlist = 0x0, statlist = 0x0, pages = 10, 
  tuples = 350, allvisfrac = 0, subroot = 0x0, subplan_params = 0x0, rel_parallel_workers = -1, serverid = 0, userid = 0, 
  useridiscurrent = false, fdwroutine = 0x0, fdw_private = 0x0, unique_for_rels = 0x0, non_unique_for_rels = 0x0, 
  baserestrictinfo = 0x1fc68d8, baserestrictcost = {startup = 0, per_tuple = 0.0050000000000000001}, 
  baserestrict_min_security = 0, joininfo = 0x0, has_eclass_joins = false, consider_partitionwise_join = false, 
  top_parent_relids = 0x1fc3608, part_scheme = 0x0, nparts = 0, boundinfo = 0x0, partition_qual = 0x0, part_rels = 0x0, 
  partexprs = 0x0, nullable_partexprs = 0x0, partitioned_child_rels = 0x0}

對于虛擬子關(guān)系(上一節(jié)介紹的被裁剪的分區(qū)),直接跳過

(gdb) n
1306            AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(l);
(gdb) 
1312            if (appinfo->parent_relid != parentRTindex)
(gdb) 
1316            childRTindex = appinfo->child_relid;
(gdb) 
1317            childRTE = root->simple_rte_array[childRTindex];
(gdb) 
1318            childrel = root->simple_rel_array[childRTindex];
(gdb) 
1326            if (!rel->consider_parallel)
(gdb) 
1332            set_rel_pathlist(root, childrel, childRTindex, childRTE);
(gdb) 
1337            if (IS_DUMMY_REL(childrel))
(gdb) 
1338                continue;

設(shè)置斷點,進入add_paths_to_append_rel函數(shù)

(gdb) b add_paths_to_append_rel
Breakpoint 2 at 0x797d88: file allpaths.c, line 1372.
(gdb) c
Continuing.

Breakpoint 2, add_paths_to_append_rel (root=0x1f1cdb8, rel=0x1fc1800, live_childrels=0x1fcfb10) at allpaths.c:1372
1372        List       *subpaths = NIL;
(gdb)

輸入?yún)?shù),其中rel是父關(guān)系,live_childrels是經(jīng)裁剪后仍存活的分區(qū)(子關(guān)系)

(gdb) n
1373        bool        subpaths_valid = true;
(gdb) p *rel
$11 = {type = T_RelOptInfo, reloptkind = RELOPT_BASEREL, relids = 0x1fc1a18, rows = 6, consider_startup = false, 
  consider_param_startup = false, consider_parallel = true, reltarget = 0x1fc1a38, pathlist = 0x0, ppilist = 0x0, 
  partial_pathlist = 0x0, cheapest_startup_path = 0x0, cheapest_total_path = 0x0, cheapest_unique_path = 0x0, 
  cheapest_parameterized_paths = 0x0, direct_lateral_relids = 0x0, lateral_relids = 0x0, relid = 1, reltablespace = 0, 
  rtekind = RTE_RELATION, min_attr = -7, max_attr = 3, attr_needed = 0x1fc1a90, attr_widths = 0x1fc1b28, 
  lateral_vars = 0x0, lateral_referencers = 0x0, indexlist = 0x0, statlist = 0x0, pages = 0, tuples = 6, allvisfrac = 0, 
  subroot = 0x0, subplan_params = 0x0, rel_parallel_workers = -1, serverid = 0, userid = 0, useridiscurrent = false, 
  fdwroutine = 0x0, fdw_private = 0x0, unique_for_rels = 0x0, non_unique_for_rels = 0x0, baserestrictinfo = 0x1fc3e20, 
  baserestrictcost = {startup = 0, per_tuple = 0}, baserestrict_min_security = 0, joininfo = 0x0, has_eclass_joins = false, 
  consider_partitionwise_join = false, top_parent_relids = 0x0, part_scheme = 0x1fc1b80, nparts = 6, boundinfo = 0x1fc1d30, 
  partition_qual = 0x0, part_rels = 0x1fc2000, partexprs = 0x1fc1f08, nullable_partexprs = 0x1fc1fe0, 
  partitioned_child_rels = 0x1fc3ea0}

初始化變量

(gdb) n
1374        List       *partial_subpaths = NIL;
(gdb) 
1375        List       *pa_partial_subpaths = NIL;
(gdb) 
1376        List       *pa_nonpartial_subpaths = NIL;
(gdb) 
1377        bool        partial_subpaths_valid = true;
(gdb) 
1379        List       *all_child_pathkeys = NIL;
(gdb) 
1380        List       *all_child_outers = NIL;
(gdb) 
1382        List       *partitioned_rels = NIL;
(gdb) 
1383        double      partial_rows = -1;
(gdb) 
1386        pa_subpaths_valid = enable_parallel_append && rel->consider_parallel;
(gdb) 
1404        if (rel->part_scheme != NULL)
(gdb) p pa_subpaths_valid
$17 = true

構(gòu)建partitioned_rels鏈表

(gdb) n
1406            if (IS_SIMPLE_REL(rel))
(gdb) 
1407                partitioned_rels = list_make1(rel->partitioned_child_rels);
(gdb) 
1433            Assert(list_length(partitioned_rels) >= 1);
(gdb) 
1441        foreach(l, live_childrels)
(gdb) p *partitioned_rels
$18 = {type = T_List, length = 1, head = 0x1fcff40, tail = 0x1fcff40}

開始遍歷live_childrels,對于每一個非虛擬子關(guān)系,記錄成本最低的訪問路徑.
如果子關(guān)系存在非參數(shù)化的總成本最低的訪問路徑,添加此路徑到我們?yōu)楦戈P(guān)系構(gòu)建的非參數(shù)化的Append訪問路徑中.

(gdb) n
1443            RelOptInfo *childrel = lfirst(l);
(gdb) 
1445            Path       *cheapest_partial_path = NULL;
(gdb) 
1451            if (rel->rtekind == RTE_SUBQUERY && childrel->partitioned_child_rels != NIL)
(gdb) 
1463            if (childrel->pathlist != NIL &&
(gdb) 
1464                childrel->cheapest_total_path->param_info == NULL)
(gdb) 
1463            if (childrel->pathlist != NIL &&
(gdb) 
1465                accumulate_append_subpath(childrel->cheapest_total_path,

同樣的思路,處理并行處理中的部分計劃

(gdb) n
1471            if (childrel->partial_pathlist != NIL)
(gdb) 
1473                cheapest_partial_path = linitial(childrel->partial_pathlist);
(gdb) 
1474                accumulate_append_subpath(cheapest_partial_path,

同樣的,處理并行append混合并行/非并行訪問路徑

(gdb) n
1486                Path       *nppath = NULL;
(gdb) 
1489                    get_cheapest_parallel_safe_total_inner(childrel->pathlist);
(gdb) 
1488                nppath =
(gdb) 
1491                if (cheapest_partial_path == NULL && nppath == NULL)
(gdb) 
1496                else if (nppath == NULL ||
(gdb) 
1498                          cheapest_partial_path->total_cost < nppath->total_cost))
(gdb) 
1497                         (cheapest_partial_path != NULL &&
(gdb) 
1501                    Assert(cheapest_partial_path != NULL);
(gdb) 
1502                    accumulate_append_subpath(cheapest_partial_path,

收集子關(guān)系所有可用的排序和參數(shù)化路徑鏈表.

(gdb) 
1534            foreach(lcp, childrel->pathlist)
(gdb) 
(gdb) n
1536                Path       *childpath = (Path *) lfirst(lcp);
(gdb) 
1537                List       *childkeys = childpath->pathkeys;
(gdb) 
1538                Relids      childouter = PATH_REQ_OUTER(childpath);
(gdb) 
1541                if (childkeys != NIL)
(gdb) 
1567                if (childouter)
(gdb) 
1534            foreach(lcp, childrel->pathlist)

繼續(xù)下一個子關(guān)系,完成處理

...
(gdb) 
1441        foreach(l, live_childrels)
(gdb) 
1598        if (subpaths_valid)

如存在子關(guān)系的非參數(shù)化訪問路徑,構(gòu)建未排序/未參數(shù)化的Append訪問路徑.

(gdb) n
1599            add_path(rel, (Path *) create_append_path(root, rel, subpaths, NIL,
    (gdb) p *rel->pathlist
Cannot access memory at address 0x0
(gdb) n
1607        if (partial_subpaths_valid)
(gdb) p *rel->pathlist
$22 = {type = T_List, length = 1, head = 0x1fd0230, tail = 0x1fd0230}

嘗試未排序/未參數(shù)化的部分Append訪問路徑.如可能,構(gòu)建parallel-aware訪問路徑.

...
(gdb) 
1641            appendpath = create_append_path(root, rel, NIL, partial_subpaths,
(gdb) 
1650            partial_rows = appendpath->path.rows;
(gdb) 
1653            add_partial_path(rel, (Path *) appendpath);
(gdb)

使用混合的部分和非部分并行的append.

1662        if (pa_subpaths_valid && pa_nonpartial_subpaths != NIL)
(gdb)

基于收集的子路徑鍵,構(gòu)建非參數(shù)化的MergeAppend訪問路徑

1701        if (subpaths_valid)
(gdb) 
1702            generate_mergeappend_paths(root, rel, live_childrels,

完成調(diào)用

(gdb) 
1719        foreach(l, all_child_outers)
(gdb) 
1757    }
(gdb) 
set_append_rel_pathlist (root=0x1f1cdb8, rel=0x1fc1800, rti=1, rte=0x1efa3d0) at allpaths.c:1354
1354    }
(gdb) p *rel->pathlist
$23 = {type = T_List, length = 1, head = 0x1fd0230, tail = 0x1fd0230}
(gdb) 
(gdb) p *(Node *)rel->pathlist->head->data.ptr_value
$24 = {type = T_AppendPath}
(gdb) p *(AppendPath *)rel->pathlist->head->data.ptr_value
$25 = {path = {type = T_AppendPath, pathtype = T_Append, parent = 0x1fc1800, pathtarget = 0x1fc1a38, param_info = 0x0, 
    parallel_aware = false, parallel_safe = true, parallel_workers = 0, rows = 6, startup_cost = 0, 
    total_cost = 30.530000000000001, pathkeys = 0x0}, partitioned_rels = 0x1fd01f8, subpaths = 0x1fcffc8, 
  first_partial_path = 2}

結(jié)束調(diào)用

(gdb) n
set_rel_pathlist (root=0x1f1cdb8, rel=0x1fc1800, rti=1, rte=0x1efa3d0) at allpaths.c:495
495     if (rel->reloptkind == RELOPT_BASEREL &&
(gdb) 
496         bms_membership(root->all_baserels) != BMS_SINGLETON)
(gdb) 
495     if (rel->reloptkind == RELOPT_BASEREL &&
(gdb) 
504     if (set_rel_pathlist_hook)
(gdb) 
508     set_cheapest(rel);
(gdb) 
513 }
(gdb)

感謝各位的閱讀!關(guān)于“PostgreSQL如何為append relation構(gòu)建訪問路徑”這篇文章就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,讓大家可以學(xué)到更多知識,如果覺得文章不錯,可以把它分享出去讓更多的人看到吧!

向AI問一下細節(jié)

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

AI