PostgreSQL 原始碼解讀(49)- 查詢語句#34(make_one_rel函式#1-概覽)

husthxd發表於2018-09-18

先前的章節已介紹了函式query_planner中子函式create_lateral_join_info、match_foreign_keys_to_quals和extract_restriction_or_clauses的主要實現邏輯,本節介紹query_planner中主計劃函式make_one_rel的主實現邏輯。

query_planner程式碼片段:

     //...

     /*
      * Ready to do the primary planning.
      */
     final_rel = make_one_rel(root, joinlist);//執行主要的計劃過程
 
     /* Check that we got at least one usable path */
     if (!final_rel || !final_rel->cheapest_total_path ||
         final_rel->cheapest_total_path->param_info != NULL)
         elog(ERROR, "failed to construct the join relation");//檢查
 
     return final_rel;//返回結果

     //...

一、資料結構

RelOptInfo

 typedef struct RelOptInfo
 {
     NodeTag     type;//節點標識
 
     RelOptKind  reloptkind;//RelOpt型別
 
     /* all relations included in this RelOptInfo */
     Relids      relids;         /*Relids(rtindex)集合 set of base relids (rangetable indexes) */
 
     /* size estimates generated by planner */
     double      rows;           /*結果元組的估算數量 estimated number of result tuples */
 
     /* per-relation planner control flags */
     bool        consider_startup;   /*是否考慮啟動成本?是,需要保留啟動成本低的路徑 keep cheap-startup-cost paths? */
     bool        consider_param_startup; /*是否考慮引數化?的路徑 ditto, for parameterized paths? */
     bool        consider_parallel;  /*是否考慮並行處理路徑 consider parallel paths? */
 
     /* default result targetlist for Paths scanning this relation */
     struct PathTarget *reltarget;   /*掃描該Relation時預設的結果 list of Vars/Exprs, cost, width */
 
     /* materialization information */
     List       *pathlist;       /*訪問路徑連結串列 Path structures */
     List       *ppilist;        /*路徑連結串列中使用引數化路徑進行 ParamPathInfos used in pathlist */
     List       *partial_pathlist;   /* partial Paths */
     struct Path *cheapest_startup_path;//代價最低的啟動路徑
     struct Path *cheapest_total_path;//代價最低的整體路徑
     struct Path *cheapest_unique_path;//代價最低的獲取唯一值的路徑
     List       *cheapest_parameterized_paths;//代價最低的引數化?路徑連結串列
 
     /* parameterization information needed for both base rels and join rels */
     /* (see also lateral_vars and lateral_referencers) */
     Relids      direct_lateral_relids;  /*使用lateral語法,需依賴的Relids rels directly laterally referenced */
     Relids      lateral_relids; /* minimum parameterization of rel */
 
     /* information about a base rel (not set for join rels!) */
     //reloptkind=RELOPT_BASEREL時使用的資料結構
     Index       relid;          /* Relation ID */
     Oid         reltablespace;  /* 表空間 containing tablespace */
     RTEKind     rtekind;        /* 基表?子查詢?還是函式等等?RELATION, SUBQUERY, FUNCTION, etc */
     AttrNumber  min_attr;       /* 最小的屬性編號 smallest attrno of rel (often <0) */
     AttrNumber  max_attr;       /* 最大的屬性編號 largest attrno of rel */
     Relids     *attr_needed;    /* 陣列 array indexed [min_attr .. max_attr] */
     int32      *attr_widths;    /* 屬性寬度 array indexed [min_attr .. max_attr] */
     List       *lateral_vars;   /* 關係依賴的Vars/PHVs LATERAL Vars and PHVs referenced by rel */
     Relids      lateral_referencers;    /*依賴該關係的Relids rels that reference me laterally */
     List       *indexlist;      /* 該關係的IndexOptInfo連結串列 list of IndexOptInfo */
     List       *statlist;       /* 統計資訊連結串列 list of StatisticExtInfo */
     BlockNumber pages;          /* 塊數 size estimates derived from pg_class */
     double      tuples;         /* 元組數 */
     double      allvisfrac;     /* ? */
     PlannerInfo *subroot;       /* 如為子查詢,儲存子查詢的root if subquery */
     List       *subplan_params; /* 如為子查詢,儲存子查詢的引數 if subquery */
     int         rel_parallel_workers;   /* 並行執行,需要多少個workers? wanted number of parallel workers */
 
     /* Information about foreign tables and foreign joins */
     //FWD相關資訊
     Oid         serverid;       /* identifies server for the table or join */
     Oid         userid;         /* identifies user to check access as */
     bool        useridiscurrent;    /* join is only valid for current user */
     /* use "struct FdwRoutine" to avoid including fdwapi.h here */
     struct FdwRoutine *fdwroutine;
     void       *fdw_private;
 
     /* cache space for remembering if we have proven this relation unique */
     //已知的,可保證唯一的Relids連結串列
     List       *unique_for_rels;    /* known unique for these other relid
                                      * set(s) */
     List       *non_unique_for_rels;    /* 已知的,不唯一的Relids連結串列 known not unique for these set(s) */
 
     /* used by various scans and joins: */
     List       *baserestrictinfo;   /* 如為基本關係,儲存約束條件 RestrictInfo structures (if base rel) */
     QualCost    baserestrictcost;   /* 解析約束表示式的成本? cost of evaluating the above */
     Index       baserestrict_min_security;  /* 最低安全等級 min security_level found in
                                              * baserestrictinfo */
     List       *joininfo;       /* 連線語句的約束條件資訊 RestrictInfo structures for join clauses
                                  * involving this rel */
     bool        has_eclass_joins;   /* 是否存在等價類連線? T means joininfo is incomplete */
 
     /* used by partitionwise joins: */
     bool        consider_partitionwise_join;    /* 分割槽? consider partitionwise
                                                  * join paths? (if
                                                  * partitioned rel) */
     Relids      top_parent_relids;  /* Relids of topmost parents (if "other"
                                      * rel) */
 
     /* used for partitioned relations */
     //分割槽表使用
     PartitionScheme part_scheme;    /* 分割槽的schema Partitioning scheme. */
     int         nparts;         /* 分割槽數 number of partitions */
     struct PartitionBoundInfoData *boundinfo;   /* 分割槽邊界資訊 Partition bounds */
     List       *partition_qual; /* 分割槽約束 partition constraint */
     struct RelOptInfo **part_rels;  /* 分割槽的RelOptInfo陣列 Array of RelOptInfos of partitions,
                                      * stored in the same order of bounds */
     List      **partexprs;      /* 非空分割槽鍵表示式 Non-nullable partition key expressions. */
     List      **nullable_partexprs; /* 可為空的分割槽鍵表示式 Nullable partition key expressions. */
     List       *partitioned_child_rels; /* RT Indexes連結串列 List of RT indexes. */
 } RelOptInfo;

二、原始碼解讀

make_one_rel
make_one_rel函式找出執行查詢的所有可能訪問路徑,但不考慮上層的Non-SPJ操作,返回一個最上層的RelOptInfo.
make_one_rel函式分為兩個階段:生成掃描路徑(set_base_rel_pathlists)和生成連線路徑(make_rel_from_joinlist).
注:SPJ是指選擇(Select)/投影(Project)/連線(Join),相對應的Non-SPJ操作是指Group分組/Sort排序等操作

 /*
  * make_one_rel
  *    Finds all possible access paths for executing a query, returning a
  *    single rel that represents the join of all base rels in the query.
  */
 RelOptInfo *
 make_one_rel(PlannerInfo *root, List *joinlist)
 {
     RelOptInfo *rel;
     Index       rti;
 
     /*
      * Construct the all_baserels Relids set.
      */
     root->all_baserels = NULL;
     for (rti = 1; rti < root->simple_rel_array_size; rti++)//遍歷RelOptInfo
     {
         RelOptInfo *brel = root->simple_rel_array[rti];
 
         /* there may be empty slots corresponding to non-baserel RTEs */
         if (brel == NULL)
             continue;
 
         Assert(brel->relid == rti); /* sanity check on array */
 
         /* ignore RTEs that are "other rels" */
         if (brel->reloptkind != RELOPT_BASEREL)
             continue;
 
         root->all_baserels = bms_add_member(root->all_baserels, brel->relid);//新增到all_baserels遍歷中
     }
 
     /* Mark base rels as to whether we care about fast-start plans */
     //設定RelOptInfo的consider_param_startup變數,是否考量fast-start plans
     set_base_rel_consider_startup(root);
 
     /*
      * Compute size estimates and consider_parallel flags for each base rel,
      * then generate access paths.
      */
     set_base_rel_sizes(root);//估算Relation的Size並且設定consider_parallel標記
     set_base_rel_pathlists(root);//生成Relation的掃描(訪問)路徑
 
     /*
      * Generate access paths for the entire join tree.
      * 透過動態規劃或遺傳演算法生成連線路徑 
      */
     rel = make_rel_from_joinlist(root, joinlist);
 
     /*
      * The result should join all and only the query's base rels.
      */
     Assert(bms_equal(rel->relids, root->all_baserels));
     //返回最上層的RelOptInfo
     return rel;
 }

//--------------------------------------------------------
 /*
  * set_base_rel_consider_startup
  *    Set the consider_[param_]startup flags for each base-relation entry.
  *
  * For the moment, we only deal with consider_param_startup here; because the
  * logic for consider_startup is pretty trivial and is the same for every base
  * relation, we just let build_simple_rel() initialize that flag correctly to
  * start with.  If that logic ever gets more complicated it would probably
  * be better to move it here.
  */
 static void
 set_base_rel_consider_startup(PlannerInfo *root)
 {
     /*
      * Since parameterized paths can only be used on the inside of a nestloop
      * join plan, there is usually little value in considering fast-start
      * plans for them.  However, for relations that are on the RHS of a SEMI
      * or ANTI join, a fast-start plan can be useful because we're only going
      * to care about fetching one tuple anyway.
      *
      * To minimize growth of planning time, we currently restrict this to
      * cases where the RHS is a single base relation, not a join; there is no
      * provision for consider_param_startup to get set at all on joinrels.
      * Also we don't worry about appendrels.  costsize.c's costing rules for
      * nestloop semi/antijoins don't consider such cases either.
      */
     ListCell   *lc;
 
     foreach(lc, root->join_info_list)
     {
         SpecialJoinInfo *sjinfo = (SpecialJoinInfo *) lfirst(lc);
         int         varno;
 
         if ((sjinfo->jointype == JOIN_SEMI || sjinfo->jointype == JOIN_ANTI) &&
             bms_get_singleton_member(sjinfo->syn_righthand, &varno))
         {
             RelOptInfo *rel = find_base_rel(root, varno);
 
             rel->consider_param_startup = true;
         }
     }
 }

//--------------------------------------------------------
 /*
  * set_base_rel_sizes
  *    Set the size estimates (rows and widths) for each base-relation entry.
  *    Also determine whether to consider parallel paths for base relations.
  *
  * We do this in a separate pass over the base rels so that rowcount
  * estimates are available for parameterized path generation, and also so
  * that each rel's consider_parallel flag is set correctly before we begin to
  * generate paths.
  */
 static void
 set_base_rel_sizes(PlannerInfo *root)
 {
     Index       rti;
 
     for (rti = 1; rti < root->simple_rel_array_size; rti++)//遍歷RelOptInfo陣列
     {
         RelOptInfo *rel = root->simple_rel_array[rti];
         RangeTblEntry *rte;
 
         /* there may be empty slots corresponding to non-baserel RTEs */
         if (rel == NULL)
             continue;
 
         Assert(rel->relid == rti);  /* sanity check on array */
 
         /* ignore RTEs that are "other rels" */
         if (rel->reloptkind != RELOPT_BASEREL)
             continue;
 
         rte = root->simple_rte_array[rti];
 
         /*
          * If parallelism is allowable for this query in general, see whether
          * it's allowable for this rel in particular.  We have to do this
          * before set_rel_size(), because (a) if this rel is an inheritance
          * parent, set_append_rel_size() will use and perhaps change the rel's
          * consider_parallel flag, and (b) for some RTE types, set_rel_size()
          * goes ahead and makes paths immediately.
          */
         if (root->glob->parallelModeOK)
             set_rel_consider_parallel(root, rel, rte);
 
         set_rel_size(root, rel, rti, rte);
     }
 }

 /*
  * set_rel_size
  *    Set size estimates for a base relation
  */
 static void
 set_rel_size(PlannerInfo *root, RelOptInfo *rel,
              Index rti, RangeTblEntry *rte)
 {
     if (rel->reloptkind == RELOPT_BASEREL &&
         relation_excluded_by_constraints(root, rel, rte))
     {
         /*
          * We proved we don't need to scan the rel via constraint exclusion,
          * so set up a single dummy path for it.  Here we only check this for
          * regular baserels; if it's an otherrel, CE was already checked in
          * set_append_rel_size().
          *
          * In this case, we go ahead and set up the relation's path right away
          * instead of leaving it for set_rel_pathlist to do.  This is because
          * we don't have a convention for marking a rel as dummy except by
          * assigning a dummy path to it.
          */
         set_dummy_rel_pathlist(rel);//
     }
     else if (rte->inh)//inherit table
     {
         /* It's an "append relation", process accordingly */
         set_append_rel_size(root, rel, rti, rte);
     }
     else
     {
         switch (rel->rtekind)
         {
             case RTE_RELATION://資料表
                 if (rte->relkind == RELKIND_FOREIGN_TABLE)//FDW
                 {
                     /* Foreign table */
                     set_foreign_size(root, rel, rte);
                 }
                 else if (rte->relkind == RELKIND_PARTITIONED_TABLE)//分割槽表
                 {
                     /*
                      * A partitioned table without any partitions is marked as
                      * a dummy rel.
                      */
                     set_dummy_rel_pathlist(rel);
                 }
                 else if (rte->tablesample != NULL)//取樣表
                 {
                     /* Sampled relation */
                     set_tablesample_rel_size(root, rel, rte);
                 }
                 else
                 {
                     /* Plain relation */
                     set_plain_rel_size(root, rel, rte);//常規的資料表
                 }
                 break;
             case RTE_SUBQUERY://子查詢
 
                 /*
                  * Subqueries don't support making a choice between
                  * parameterized and unparameterized paths, so just go ahead
                  * and build their paths immediately.
                  */
                 set_subquery_pathlist(root, rel, rti, rte);//生成子查詢訪問路徑
                 break;
             case RTE_FUNCTION://FUNCTION
                 set_function_size_estimates(root, rel);
                 break;
             case RTE_TABLEFUNC://TABLEFUNC
                 set_tablefunc_size_estimates(root, rel);
                 break;
             case RTE_VALUES://VALUES
                 set_values_size_estimates(root, rel);
                 break;
             case RTE_CTE://CTE
 
                 /*
                  * CTEs don't support making a choice between parameterized
                  * and unparameterized paths, so just go ahead and build their
                  * paths immediately.
                  */
                 if (rte->self_reference)
                     set_worktable_pathlist(root, rel, rte);
                 else
                     set_cte_pathlist(root, rel, rte);
                 break;
             case RTE_NAMEDTUPLESTORE://NAMEDTUPLESTORE,命名元組儲存
                 set_namedtuplestore_pathlist(root, rel, rte);
                 break;
             default:
                 elog(ERROR, "unexpected rtekind: %d", (int) rel->rtekind);
                 break;
         }
     }
 
     /*
      * We insist that all non-dummy rels have a nonzero rowcount estimate.
      */
     Assert(rel->rows > 0 || IS_DUMMY_REL(rel));
 }
 
//--------------------------------------------------------
 /*
  * set_base_rel_pathlists
  *    Finds all paths available for scanning each base-relation entry.
  *    Sequential scan and any available indices are considered.
  *    Each useful path is attached to its relation's 'pathlist' field.
  *
  *    為每一個base rels找出所有可用的訪問路徑(順序掃描和所有可用的索引都會考慮在內)
  *    每一個可用的路徑都會新增到pathlist連結串列中
  *
  */
 static void
 set_base_rel_pathlists(PlannerInfo *root)
 {
     Index       rti;
 
     for (rti = 1; rti < root->simple_rel_array_size; rti++)//遍歷RelOptInfo陣列
     {
         RelOptInfo *rel = root->simple_rel_array[rti];
 
         /* there may be empty slots corresponding to non-baserel RTEs */
         if (rel == NULL)
             continue;
 
         Assert(rel->relid == rti);  /* sanity check on array */
 
         /* ignore RTEs that are "other rels" */
         if (rel->reloptkind != RELOPT_BASEREL)
             continue;
 
         set_rel_pathlist(root, rel, rti, root->simple_rte_array[rti]);
     }
 }

 /*
  * set_rel_pathlist
  *    Build access paths for a base relation
  */
 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)//inherit
     {
         /* It's an "append relation", process accordingly */
         set_append_rel_pathlist(root, rel, rti, rte);
     }
     else//常規
     {
         switch (rel->rtekind)
         {
             case RTE_RELATION://資料表
                 if (rte->relkind == RELKIND_FOREIGN_TABLE)//FDW
                 {
                     /* Foreign table */
                     set_foreign_pathlist(root, rel, rte);
                 }
                 else if (rte->tablesample != NULL)//取樣表
                 {
                     /* Sampled relation */
                     set_tablesample_rel_pathlist(root, rel, rte);
                 }
                 else//常規資料表
                 {
                     /* Plain relation */
                     set_plain_rel_pathlist(root, rel, rte);
                 }
                 break;
             case RTE_SUBQUERY://子查詢
                 /* Subquery --- 已在set_rel_size處理,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.
      *
      * 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.
      *
      * 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).
      */
     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.
      */
     if (set_rel_pathlist_hook)//鉤子函式
         (*set_rel_pathlist_hook) (root, rel, rti, rte);
 
     /* Now find the cheapest of the paths for this rel */
     set_cheapest(rel);//找出代價最低的訪問路徑
 
 #ifdef OPTIMIZER_DEBUG
     debug_print_rel(root, rel);
 #endif
 }

//------------------------------------------------------------
 /*
  * make_rel_from_joinlist
  *    Build access paths using a "joinlist" to guide the join path search.
  *
  *    根據joinlist構建連線訪問路徑,joinlist是函式deconstruct_jointree函式的返回
  *
  * See comments for deconstruct_jointree() for definition of the joinlist
  * data structure.
  */
 static RelOptInfo *
 make_rel_from_joinlist(PlannerInfo *root, List *joinlist)
 {
     int         levels_needed;
     List       *initial_rels;
     ListCell   *jl;
 
     /*
      * Count the number of child joinlist nodes.  This is the depth of the
      * dynamic-programming algorithm we must employ to consider all ways of
      * joining the child nodes.
      */
     levels_needed = list_length(joinlist);//joinlist連結串列長度
 
     if (levels_needed <= 0)
         return NULL;            /* nothing to do? */
 
     /*
      * Construct a list of rels corresponding to the child joinlist nodes.
      * This may contain both base rels and rels constructed according to
      * sub-joinlists.
      */
     initial_rels = NIL;
     foreach(jl, joinlist)//遍歷連結串列
     {
         Node       *jlnode = (Node *) lfirst(jl);
         RelOptInfo *thisrel;
 
         if (IsA(jlnode, RangeTblRef))//RTR
         {
             int         varno = ((RangeTblRef *) jlnode)->rtindex;
 
             thisrel = find_base_rel(root, varno);
         }
         else if (IsA(jlnode, List))
         {
             /* Recurse to handle subproblem */
             thisrel = make_rel_from_joinlist(root, (List *) jlnode);//遞迴呼叫
         }
         else
         {
             elog(ERROR, "unrecognized joinlist node type: %d",
                  (int) nodeTag(jlnode));
             thisrel = NULL;     /* keep compiler quiet */
         }
 
         initial_rels = lappend(initial_rels, thisrel);//加入初始化連結串列中
     }
 
     if (levels_needed == 1)
     {
         /*
          * Single joinlist node, so we're done.
          */
         return (RelOptInfo *) linitial(initial_rels);
     }
     else
     {
         /*
          * Consider the different orders in which we could join the rels,
          * using a plugin, GEQO, or the regular join search code.
          *
          * We put the initial_rels list into a PlannerInfo field because
          * has_legal_joinclause() needs to look at it (ugly :-().
          */
         root->initial_rels = initial_rels;
 
         if (join_search_hook)//鉤子函式
             return (*join_search_hook) (root, levels_needed, initial_rels);
         else if (enable_geqo && levels_needed >= geqo_threshold)
             return geqo(root, levels_needed, initial_rels);//透過遺傳演算法構建連線訪問路徑
         else
             return standard_join_search(root, levels_needed, initial_rels);//透過動態規劃演算法構建連線路徑
     }
 }

三、參考資料

allpaths.c

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/6906/viewspace-2374862/,如需轉載,請註明出處,否則將追究法律責任。

相關文章