您好,登錄后才能下訂單哦!
本篇內容介紹了“PostgreSQL中set_base_rel_sizes函數及其子函數案例分析”的有關知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠學有所成!
make_one_rel源代碼:
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; }
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函數調用了set_base_rel_sizes,該函數的主要實現邏輯通過調用set_rel_size實現.現重點考察函數set_rel_size中對基礎關系進行估算的處理邏輯,即函數set_plain_rel_size的實現邏輯.
set_plain_rel_size
/* * set_plain_rel_size * Set size estimates for a plain relation (no subquery, no inheritance) */ static void set_plain_rel_size(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte) { /* * Test any partial indexes of rel for applicability. We must do this * first since partial unique indexes can affect size estimates. */ check_index_predicates(root, rel);//驗證部分(條件)索引的可用性 /* Mark rel with estimated output rows, width, etc */ set_baserel_size_estimates(root, rel);//標記rel的輸出行數/行寬等信息 }
set_plain_rel_size->check_index_predicates
如果部分(條件)索引的謂詞與查詢語句相匹配,則predOK設置為true
比如:
數據表t1(c1 int,c2 varchar(40),...),如存在索引idx_t1_partial_c1,條件為where c1 > 100
查詢條件為where c1 > 100(是否支持>200?),那么該謂詞與查詢條件相匹配
//----------------------------------------- check_index_predicates /* * check_index_predicates * Set the predicate-derived IndexOptInfo fields for each index * of the specified relation. * * predOK is set true if the index is partial and its predicate is satisfied * for this query, ie the query's WHERE clauses imply the predicate. * 如果部分(條件)索引的謂詞與查詢語句相匹配,則predOK設置為true * 比如: * 數據表t1(c1 int,c2 varchar(40),...),如存在索引idx_t1_partial_c1,條件為where c1 > 100 * 查詢條件為where c1 > 100(是否支持>200?),那么該謂詞與查詢條件相匹配 * * indrestrictinfo is set to the relation's baserestrictinfo list less any * conditions that are implied by the index's predicate. (Obviously, for a * non-partial index, this is the same as baserestrictinfo.) Such conditions * can be dropped from the plan when using the index, in certain cases. * * indrestrictinfo會加入到rel的baserestrictinfo鏈表中,減少了索引謂詞所隱含的限制條件. * * At one time it was possible for this to get re-run after adding more * restrictions to the rel, thus possibly letting us prove more indexes OK. * That doesn't happen any more (at least not in the core code's usage), * but this code still supports it in case extensions want to mess with the * baserestrictinfo list. We assume that adding more restrictions can't make * an index not predOK. We must recompute indrestrictinfo each time, though, * to make sure any newly-added restrictions get into it if needed. */ void check_index_predicates(PlannerInfo *root, RelOptInfo *rel) { List *clauselist;//條件鏈表 bool have_partial;//是否包含部分索引 bool is_target_rel;//目標rel? Relids otherrels;//Relids ListCell *lc;//臨時變量 /* Indexes are available only on base or "other" member relations. */ Assert(IS_SIMPLE_REL(rel));//rel必須是基礎關系 /* * Initialize the indrestrictinfo lists to be identical to * baserestrictinfo, and check whether there are any partial indexes. If * not, this is all we need to do. */ have_partial = false; foreach(lc, rel->indexlist)//遍歷index { IndexOptInfo *index = (IndexOptInfo *) lfirst(lc); index->indrestrictinfo = rel->baserestrictinfo;//設置索引約束條件 if (index->indpred) have_partial = true;//存在部分索引 } if (!have_partial) return; /* * Construct a list of clauses that we can assume true for the purpose of * proving the index(es) usable. Restriction clauses for the rel are * always usable, and so are any join clauses that are "movable to" this * rel. Also, we can consider any EC-derivable join clauses (which must * be "movable to" this rel, by definition). */ clauselist = list_copy(rel->baserestrictinfo);//條件語句初始化 /* Scan the rel's join clauses */ foreach(lc, rel->joininfo)//遍歷連接條件 { RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);//條件 /* Check if clause can be moved to this rel */ if (!join_clause_is_movable_to(rinfo, rel))//條件是否可以下推到Rel中 continue;//不可用,下一個條件 clauselist = lappend(clauselist, rinfo);//可以,則添加到條件語句鏈表中 } /* * Add on any equivalence-derivable join clauses. Computing the correct * relid sets for generate_join_implied_equalities is slightly tricky * because the rel could be a child rel rather than a true baserel, and in * that case we must remove its parents' relid(s) from all_baserels. */ if (rel->reloptkind == RELOPT_OTHER_MEMBER_REL) otherrels = bms_difference(root->all_baserels, find_childrel_parents(root, rel));// else otherrels = bms_difference(root->all_baserels, rel->relids);//獲取與rel無關的其他rels if (!bms_is_empty(otherrels)) clauselist = list_concat(clauselist, generate_join_implied_equalities(root, bms_union(rel->relids, otherrels), otherrels, rel));//添加到條件語句 /* * Normally we remove quals that are implied by a partial index's * predicate from indrestrictinfo, indicating that they need not be * checked explicitly by an indexscan plan using this index. However, if * the rel is a target relation of UPDATE/DELETE/SELECT FOR UPDATE, we * cannot remove such quals from the plan, because they need to be in the * plan so that they will be properly rechecked by EvalPlanQual testing. * Some day we might want to remove such quals from the main plan anyway * and pass them through to EvalPlanQual via a side channel; but for now, * we just don't remove implied quals at all for target relations. */ is_target_rel = (rel->relid == root->parse->resultRelation || get_plan_rowmark(root->rowMarks, rel->relid) != NULL); /* * Now try to prove each index predicate true, and compute the * indrestrictinfo lists for partial indexes. Note that we compute the * indrestrictinfo list even for non-predOK indexes; this might seem * wasteful, but we may be able to use such indexes in OR clauses, cf * generate_bitmap_or_paths(). */ foreach(lc, rel->indexlist)//遍歷index { IndexOptInfo *index = (IndexOptInfo *) lfirst(lc); ListCell *lcr; if (index->indpred == NIL) continue; /* ignore non-partial indexes here */ if (!index->predOK) /* don't repeat work if already proven OK */ index->predOK = predicate_implied_by(index->indpred, clauselist, false);//設置predOK參數 /* If rel is an update target, leave indrestrictinfo as set above */ if (is_target_rel) continue; /* Else compute indrestrictinfo as the non-implied quals */ index->indrestrictinfo = NIL; foreach(lcr, rel->baserestrictinfo) { RestrictInfo *rinfo = (RestrictInfo *) lfirst(lcr); /* predicate_implied_by() assumes first arg is immutable */ if (contain_mutable_functions((Node *) rinfo->clause) || !predicate_implied_by(list_make1(rinfo->clause), index->indpred, false)) index->indrestrictinfo = lappend(index->indrestrictinfo, rinfo);// } } }
set_plain_rel_size->set_baserel_size_estimates
函數注釋:
//----------------------------------------- set_baserel_size_estimates /* * set_baserel_size_estimates * Set the size estimates for the given base relation. * 估算base rel的估算大小 * * The rel's targetlist and restrictinfo list must have been constructed * already, and rel->tuples must be set. * rel的targetlist和限制條件鏈表已構建,并且tuples已獲取. * * We set the following fields of the rel node: * 通過該方法,設置以下3個變量 * rows: the estimated number of output tuples (after applying * restriction clauses). * 應用限制條件后,估算得出的輸出元組數目 * width: the estimated average output tuple width in bytes. * 以字節為單位輸出估算的平均元組大小 * baserestrictcost: estimated cost of evaluating baserestrictinfo clauses. * 解析限制條件的估算成本 */
源代碼:
void set_baserel_size_estimates(PlannerInfo *root, RelOptInfo *rel) { double nrows; /* Should only be applied to base relations */ Assert(rel->relid > 0); nrows = rel->tuples * clauselist_selectivity(root, rel->baserestrictinfo, 0, JOIN_INNER, NULL);//元組總數*選擇率 rel->rows = clamp_row_est(nrows); cost_qual_eval(&rel->baserestrictcost, rel->baserestrictinfo, root); set_rel_width(root, rel); } //----------------------------------------- clauselist_selectivity /* * clauselist_selectivity - * Compute the selectivity of an implicitly-ANDed list of boolean * expression clauses. The list can be empty, in which case 1.0 * must be returned. List elements may be either RestrictInfos * or bare expression clauses --- the former is preferred since * it allows caching of results. * * 計算布爾表達式條件(隱式AND條件語句鏈表保存)的選擇性.如果鏈表為空, * 則返回1.0.鏈表中的元素可以是RestrictInfo結構體,也可以是裸條件表達式 * (前者因為允許緩存,因此是首選) * * See clause_selectivity() for the meaning of the additional parameters. * * Our basic approach is to take the product of the selectivities of the * subclauses. However, that's only right if the subclauses have independent * probabilities, and in reality they are often NOT independent. So, * we want to be smarter where we can. * * 采取的基本方法是subclauses的選擇性乘積。采取這種做法的前臺是子項具有獨立的概率. * 但實際上它們往往不是獨立的,因此,需要在能做到的地方變得更好。 * * If the clauses taken together refer to just one relation, we'll try to * apply selectivity estimates using any extended statistics for that rel. * Currently we only have (soft) functional dependencies, so apply these in as * many cases as possible, and fall back on normal estimates for remaining * clauses. * * 如果在一起的子句只涉及一個關系,將嘗試應用關系上的擴展統計進行選擇性的估算。 * * We also recognize "range queries", such as "x > 34 AND x < 42". Clauses * are recognized as possible range query components if they are restriction * opclauses whose operators have scalarltsel or a related function as their * restriction selectivity estimator. We pair up clauses of this form that * refer to the same variable. An unpairable clause of this kind is simply * multiplied into the selectivity product in the normal way. But when we * find a pair, we know that the selectivities represent the relative * positions of the low and high bounds within the column's range, so instead * of figuring the selectivity as hisel * losel, we can figure it as hisel + * losel - 1. (To visualize this, see that hisel is the fraction of the range * below the high bound, while losel is the fraction above the low bound; so * hisel can be interpreted directly as a 0..1 value but we need to convert * losel to 1-losel before interpreting it as a value. Then the available * range is 1-losel to hisel. However, this calculation double-excludes * nulls, so really we need hisel + losel + null_frac - 1.) * * 優化器還可以識別范圍查詢,比如x > 34 AND x < 42,這類范圍查詢不能簡單的把x > 34 * 的選擇率乘以x < 42的選擇率,為方便起見,假定x < 42的選擇率為hisel,x < 34的選擇率為losel, * 那么計算公式應該為hisel - (1 - losel),即hisel + losel -1,考慮NULL值,則范圍查詢的選擇率 * 為hisel + losel + null_frac - 1 * * If either selectivity is exactly DEFAULT_INEQ_SEL, we forget this equation * and instead use DEFAULT_RANGE_INEQ_SEL. The same applies if the equation * yields an impossible (negative) result. * * 如果任意一個選擇性都恰好是DEFAULT_INEQ_SEL,那么我們將忘記這個等式, * 而使用DEFAULT_RANGE_INEQ_SEL。這種情況同樣適用于如果等式產生了一個不可能的(負的)結果。 * * A free side-effect is that we can recognize redundant inequalities such * as "x < 4 AND x < 5"; only the tighter constraint will be counted. * * 我們可以識別冗余的不等式,比如x < 4 AND x <5,只有嚴格的約束條件才會計算在內 * * Of course this is all very dependent on the behavior of the inequality * selectivity functions; perhaps some day we can generalize the approach. * * 這完全取決于不等選擇性函數的行為,也許有一天我們可以推廣這種方法. * */ Selectivity clauselist_selectivity(PlannerInfo *root, List *clauses, int varRelid, JoinType jointype, SpecialJoinInfo *sjinfo) { Selectivity s1 = 1.0;//默認返回值 RelOptInfo *rel;// Bitmapset *estimatedclauses = NULL;//位圖集合 RangeQueryClause *rqlist = NULL;//范圍查詢語句 ListCell *l;//臨時變量 int listidx; /* * If there's exactly one clause, just go directly to * clause_selectivity(). None of what we might do below is relevant. */ if (list_length(clauses) == 1) return clause_selectivity(root, (Node *) linitial(clauses), varRelid, jointype, sjinfo);//單個條件 /* * Determine if these clauses reference a single relation. If so, and if * it has extended statistics, try to apply those. */ //如果條件鏈表中的元素依賴的rel有且只有一個,則返回此rel rel = find_single_rel_for_clauses(root, clauses); //應用dependencies_clauselist_selectivity中的可用的條件進行選擇率估算 if (rel && rel->rtekind == RTE_RELATION && rel->statlist != NIL) { /* * Perform selectivity estimations on any clauses found applicable by * dependencies_clauselist_selectivity. 'estimatedclauses' will be * filled with the 0-based list positions of clauses used that way, so * that we can ignore them below. */ s1 *= dependencies_clauselist_selectivity(root, clauses, varRelid, jointype, sjinfo, rel, &estimatedclauses); /* * This would be the place to apply any other types of extended * statistics selectivity estimations for remaining clauses. */ } /* * Apply normal selectivity estimates for remaining clauses. We'll be * careful to skip any clauses which were already estimated above. * 剩下的條件語句,應用常規的選擇率估算. * * Anything that doesn't look like a potential rangequery clause gets * multiplied into s1 and forgotten. Anything that does gets inserted into * an rqlist entry. * 非范圍條件語句乘上s1后被丟棄,范圍條件語句則加入到rqlist鏈表中. */ listidx = -1; foreach(l, clauses)//遍歷 { Node *clause = (Node *) lfirst(l);//鏈表中的元素 RestrictInfo *rinfo; Selectivity s2; listidx++; /* * Skip this clause if it's already been estimated by some other * statistics above. */ if (bms_is_member(listidx, estimatedclauses))//跳過已處理的條件 continue; /* Always compute the selectivity using clause_selectivity */ s2 = clause_selectivity(root, clause, varRelid, jointype, sjinfo);//獲取條件選擇率 /* * Check for being passed a RestrictInfo. * * If it's a pseudoconstant RestrictInfo, then s2 is either 1.0 or * 0.0; just use that rather than looking for range pairs. */ if (IsA(clause, RestrictInfo))//條件語句是RestrictInfo類型 { rinfo = (RestrictInfo *) clause; if (rinfo->pseudoconstant)//常量 { s1 = s1 * s2;//直接相乘 continue; } clause = (Node *) rinfo->clause;//條件表達式 } else rinfo = NULL;//不是RestrictInfo類型,rinfo設置為NULL /* * See if it looks like a restriction clause with a pseudoconstant on * one side. (Anything more complicated than that might not behave in * the simple way we are expecting.) Most of the tests here can be * done more efficiently with rinfo than without. */ if (is_opclause(clause) && list_length(((OpExpr *) clause)->args) == 2)//OpExpr { OpExpr *expr = (OpExpr *) clause;//條件語句 bool varonleft = true; bool ok; if (rinfo)//rinfo中的條件語句 { ok = (bms_membership(rinfo->clause_relids) == BMS_SINGLETON) && (is_pseudo_constant_clause_relids(lsecond(expr->args), rinfo->right_relids) || (varonleft = false, is_pseudo_constant_clause_relids(linitial(expr->args), rinfo->left_relids))); } else//裸條件語句 { ok = (NumRelids(clause) == 1) && (is_pseudo_constant_clause(lsecond(expr->args)) || (varonleft = false, is_pseudo_constant_clause(linitial(expr->args)))); } if (ok)//校驗通過 { /* * If it's not a "<"/"<="/">"/">=" operator, just merge the * selectivity in generically. But if it's the right oprrest, * add the clause to rqlist for later processing. */ switch (get_oprrest(expr->opno)) { case F_SCALARLTSEL: case F_SCALARLESEL: addRangeClause(&rqlist, clause, varonleft, true, s2);//范圍條件 break; case F_SCALARGTSEL: case F_SCALARGESEL: addRangeClause(&rqlist, clause, varonleft, false, s2);//范圍條件 break; default: /* Just merge the selectivity in generically */ s1 = s1 * s2;//直接相乘 break; } continue; /* drop to loop bottom */ } } /* Not the right form, so treat it generically. */ s1 = s1 * s2;//直接相乘 } /* * Now scan the rangequery pair list. */ while (rqlist != NULL)//處理范圍條件 { RangeQueryClause *rqnext; if (rqlist->have_lobound && rqlist->have_hibound)//存在上下限 { /* Successfully matched a pair of range clauses */ Selectivity s2;//選擇率 /* * Exact equality to the default value probably means the * selectivity function punted. This is not airtight but should * be good enough. */ if (rqlist->hibound == DEFAULT_INEQ_SEL || rqlist->lobound == DEFAULT_INEQ_SEL)//默認值 { s2 = DEFAULT_RANGE_INEQ_SEL;//默認為DEFAULT_RANGE_INEQ_SEL } else { s2 = rqlist->hibound + rqlist->lobound - 1.0;//計算公式在注釋已解釋 /* Adjust for double-exclusion of NULLs */ s2 += nulltestsel(root, IS_NULL, rqlist->var, varRelid, jointype, sjinfo);//NULL值 /* * A zero or slightly negative s2 should be converted into a * small positive value; we probably are dealing with a very * tight range and got a bogus result due to roundoff errors. * However, if s2 is very negative, then we probably have * default selectivity estimates on one or both sides of the * range that we failed to recognize above for some reason. */ if (s2 <= 0.0)//小于0? { if (s2 < -0.01) { /* * No data available --- use a default estimate that * is small, but not real small. */ s2 = DEFAULT_RANGE_INEQ_SEL;//小于﹣1%,默認值 } else { /* * It's just roundoff error; use a small positive * value */ s2 = 1.0e-10;,//否則設置為1的﹣10次方 } } } /* Merge in the selectivity of the pair of clauses */ s1 *= s2;//直接相乘 } else//只有其中一個限制 { /* Only found one of a pair, merge it in generically */ if (rqlist->have_lobound) s1 *= rqlist->lobound;//下限 else s1 *= rqlist->hibound;//上限 } /* release storage and advance */ rqnext = rqlist->next;//釋放資源 pfree(rqlist); rqlist = rqnext; } return s1;//返回選擇率 } /* * clause_selectivity - * Compute the selectivity of a general boolean expression clause. * 計算布爾表達式條件語句的選擇率 * * The clause can be either a RestrictInfo or a plain expression. If it's * a RestrictInfo, we try to cache the selectivity for possible re-use, * so passing RestrictInfos is preferred. * clause可以是RestrictInfo結構體或者是常規的表達式.如果是RestrictInfo, * 那么我們會嘗試緩存結果已便于重用. * * varRelid is either 0 or a rangetable index. * varRelid是0或者是RTE編號 * * When varRelid is not 0, only variables belonging to that relation are * considered in computing selectivity; other vars are treated as constants * of unknown values. This is appropriate for estimating the selectivity of * a join clause that is being used as a restriction clause in a scan of a * nestloop join's inner relation --- varRelid should then be the ID of the * inner relation. * 如果varRelid不為0,只有屬于該Rel的變量才會考慮計算選擇率,其他變量作為未知常量處理 * 這種情況常見于嵌套循環內表的選擇率估算(varRelid是內部的RTE) * * When varRelid is 0, all variables are treated as variables. This * is appropriate for ordinary join clauses and restriction clauses. * 如果varRelid為0,所有的變量都需要考慮,用于常規的連接條件和限制條件估算 * * * jointype is the join type, if the clause is a join clause. Pass JOIN_INNER * if the clause isn't a join clause. * 如果條件是連接條件,那么jointype是連接類型,如果不是連接調整,則該參數為JOIN_INNER * * sjinfo is NULL for a non-join clause, otherwise it provides additional * context information about the join being performed. There are some * special cases: * 1. For a special (not INNER) join, sjinfo is always a member of * root->join_info_list.非INNER_JOIN,sjinfo通常是oot->join_info_list的一個元素 * 2. For an INNER join, sjinfo is just a transient struct, and only the * relids and jointype fields in it can be trusted.INNER_JOIN,sjinfo通常是臨時的結構 * It is possible for jointype to be different from sjinfo->jointype.//jointype可能跟sjinfo中的jointype不同 * This indicates we are considering a variant join: either with * the LHS and RHS switched, or with one input unique-ified.這意味著連接有可能會變換 * * Note: when passing nonzero varRelid, it's normally appropriate to set * jointype == JOIN_INNER, sjinfo == NULL, even if the clause is really a * join clause; because we aren't treating it as a join clause. */ Selectivity clause_selectivity(PlannerInfo *root, Node *clause, int varRelid, JoinType jointype, SpecialJoinInfo *sjinfo) { Selectivity s1 = 0.5; /* 默認值,default for any unhandled clause type */ RestrictInfo *rinfo = NULL; bool cacheable = false; if (clause == NULL) /* can this still happen? */ return s1; if (IsA(clause, RestrictInfo))//RestrictInfo { rinfo = (RestrictInfo *) clause; /* * If the clause is marked pseudoconstant, then it will be used as a * gating qual and should not affect selectivity estimates; hence * return 1.0. The only exception is that a constant FALSE may be * taken as having selectivity 0.0, since it will surely mean no rows * out of the plan. This case is simple enough that we need not * bother caching the result. */ if (rinfo->pseudoconstant)//pseudoconstant,不影響選擇率 { if (!IsA(rinfo->clause, Const)) return (Selectivity) 1.0;//返回1.0 } /* * If the clause is marked redundant, always return 1.0. */ if (rinfo->norm_selec > 1) return (Selectivity) 1.0;//返回1.0 /* * If possible, cache the result of the selectivity calculation for * the clause. We can cache if varRelid is zero or the clause * contains only vars of that relid --- otherwise varRelid will affect * the result, so mustn't cache. Outer join quals might be examined * with either their join's actual jointype or JOIN_INNER, so we need * two cache variables to remember both cases. Note: we assume the * result won't change if we are switching the input relations or * considering a unique-ified case, so we only need one cache variable * for all non-JOIN_INNER cases. */ if (varRelid == 0 || bms_is_subset_singleton(rinfo->clause_relids, varRelid))//varRelid為0 { /* Cacheable --- do we already have the result? */ if (jointype == JOIN_INNER) { if (rinfo->norm_selec >= 0) return rinfo->norm_selec; } else { if (rinfo->outer_selec >= 0) return rinfo->outer_selec; } cacheable = true; } /* * Proceed with examination of contained clause. If the clause is an * OR-clause, we want to look at the variant with sub-RestrictInfos, * so that per-subclause selectivities can be cached. */ if (rinfo->orclause) clause = (Node *) rinfo->orclause; else clause = (Node *) rinfo->clause; } if (IsA(clause, Var))//Var { Var *var = (Var *) clause; /* * We probably shouldn't ever see an uplevel Var here, but if we do, * return the default selectivity... */ if (var->varlevelsup == 0 && (varRelid == 0 || varRelid == (int) var->varno)) { /* Use the restriction selectivity function for a bool Var */ s1 = boolvarsel(root, (Node *) var, varRelid);//bool Var選擇率 } } else if (IsA(clause, Const))//常量 { /* bool constant is pretty easy... */ Const *con = (Const *) clause; s1 = con->constisnull ? 0.0 : DatumGetBool(con->constvalue) ? 1.0 : 0.0;//常量有效則為1.0,否則為0.0 } else if (IsA(clause, Param))//參數 { /* see if we can replace the Param */ Node *subst = estimate_expression_value(root, clause); if (IsA(subst, Const)) { /* bool constant is pretty easy... */ Const *con = (Const *) subst; s1 = con->constisnull ? 0.0 : DatumGetBool(con->constvalue) ? 1.0 : 0.0; } else { /* XXX any way to do better than default? */ } } else if (not_clause(clause))//反選 { /* inverse of the selectivity of the underlying clause */ s1 = 1.0 - clause_selectivity(root, (Node *) get_notclausearg((Expr *) clause), varRelid, jointype, sjinfo); } else if (and_clause(clause))//AND語句 { /* share code with clauselist_selectivity() */ s1 = clauselist_selectivity(root, ((BoolExpr *) clause)->args, varRelid, jointype, sjinfo);//遞歸調用 } else if (or_clause(clause))//OR語句 { /* * Selectivities for an OR clause are computed as s1+s2 - s1*s2 to * account for the probable overlap of selected tuple sets. * * XXX is this too conservative? */ ListCell *arg; s1 = 0.0; foreach(arg, ((BoolExpr *) clause)->args) { Selectivity s2 = clause_selectivity(root, (Node *) lfirst(arg), varRelid, jointype, sjinfo);//遞歸調用 s1 = s1 + s2 - s1 * s2; } } else if (is_opclause(clause) || IsA(clause, DistinctExpr)) { OpExpr *opclause = (OpExpr *) clause; Oid opno = opclause->opno; if (treat_as_join_clause(clause, rinfo, varRelid, sjinfo)) { /* Estimate selectivity for a join clause. */ s1 = join_selectivity(root, opno, opclause->args, opclause->inputcollid, jointype, sjinfo);//連接條件 } else { /* Estimate selectivity for a restriction clause. */ s1 = restriction_selectivity(root, opno, opclause->args, opclause->inputcollid, varRelid);//限制條件 } /* * DistinctExpr has the same representation as OpExpr, but the * contained operator is "=" not "<>", so we must negate the result. * This estimation method doesn't give the right behavior for nulls, * but it's better than doing nothing. */ if (IsA(clause, DistinctExpr)) s1 = 1.0 - s1;//Distinct? } else if (IsA(clause, ScalarArrayOpExpr))//數組 { /* Use node specific selectivity calculation function */ s1 = scalararraysel(root, (ScalarArrayOpExpr *) clause, treat_as_join_clause(clause, rinfo, varRelid, sjinfo), varRelid, jointype, sjinfo); } else if (IsA(clause, RowCompareExpr))//行對比 { /* Use node specific selectivity calculation function */ s1 = rowcomparesel(root, (RowCompareExpr *) clause, varRelid, jointype, sjinfo); } else if (IsA(clause, NullTest))//NullTest { /* Use node specific selectivity calculation function */ s1 = nulltestsel(root, ((NullTest *) clause)->nulltesttype, (Node *) ((NullTest *) clause)->arg, varRelid, jointype, sjinfo); } else if (IsA(clause, BooleanTest))//BooleanTest { /* Use node specific selectivity calculation function */ s1 = booltestsel(root, ((BooleanTest *) clause)->booltesttype, (Node *) ((BooleanTest *) clause)->arg, varRelid, jointype, sjinfo); } else if (IsA(clause, CurrentOfExpr))//CurrentOfExpr { /* CURRENT OF selects at most one row of its table */ CurrentOfExpr *cexpr = (CurrentOfExpr *) clause; RelOptInfo *crel = find_base_rel(root, cexpr->cvarno); if (crel->tuples > 0) s1 = 1.0 / crel->tuples; } else if (IsA(clause, RelabelType))//RelabelType { /* Not sure this case is needed, but it can't hurt */ s1 = clause_selectivity(root, (Node *) ((RelabelType *) clause)->arg, varRelid, jointype, sjinfo); } else if (IsA(clause, CoerceToDomain))//CoerceToDomain { /* Not sure this case is needed, but it can't hurt */ s1 = clause_selectivity(root, (Node *) ((CoerceToDomain *) clause)->arg, varRelid, jointype, sjinfo); } else { /* * For anything else, see if we can consider it as a boolean variable. * This only works if it's an immutable expression in Vars of a single * relation; but there's no point in us checking that here because * boolvarsel() will do it internally, and return a suitable default * selectivity if not. */ s1 = boolvarsel(root, clause, varRelid);//默認為bool Var } /* Cache the result if possible */ if (cacheable)//緩存? { if (jointype == JOIN_INNER) rinfo->norm_selec = s1; else rinfo->outer_selec = s1; } #ifdef SELECTIVITY_DEBUG elog(DEBUG4, "clause_selectivity: s1 %f", s1); #endif /* SELECTIVITY_DEBUG */ return s1; } /* * find_single_rel_for_clauses * Examine each clause in 'clauses' and determine if all clauses * reference only a single relation. If so return that relation, * otherwise return NULL. * * 如果條件鏈表中的元素依賴的rel有且只有一個,則返回此rel */ static RelOptInfo * find_single_rel_for_clauses(PlannerInfo *root, List *clauses) { int lastrelid = 0; ListCell *l; foreach(l, clauses) { RestrictInfo *rinfo = (RestrictInfo *) lfirst(l); int relid; /* * If we have a list of bare clauses rather than RestrictInfos, we * could pull out their relids the hard way with pull_varnos(). * However, currently the extended-stats machinery won't do anything * with non-RestrictInfo clauses anyway, so there's no point in * spending extra cycles; just fail if that's what we have. */ if (!IsA(rinfo, RestrictInfo)) return NULL; if (bms_is_empty(rinfo->clause_relids)) continue; /* we can ignore variable-free clauses */ if (!bms_get_singleton_member(rinfo->clause_relids, &relid)) return NULL; /* multiple relations in this clause */ if (lastrelid == 0) lastrelid = relid; /* first clause referencing a relation */ else if (relid != lastrelid) return NULL; /* relation not same as last one */ } if (lastrelid != 0) return find_base_rel(root, lastrelid); return NULL; /* no clauses */ } //---------------------------------------------- clamp_row_est /* * clamp_row_est * Force a row-count estimate to a sane value. * 返回合法值 */ double clamp_row_est(double nrows) { /* * Force estimate to be at least one row, to make explain output look * better and to avoid possible divide-by-zero when interpolating costs. * Make it an integer, too. */ if (nrows <= 1.0) nrows = 1.0;//小于1,返回1 else nrows = rint(nrows);//整型 return nrows; } //---------------------------------------------- cost_qual_eval /* * cost_qual_eval * Estimate the CPU costs of evaluating a WHERE clause. * The input can be either an implicitly-ANDed list of boolean * expressions, or a list of RestrictInfo nodes. (The latter is * preferred since it allows caching of the results.) * The result includes both a one-time (startup) component, * and a per-evaluation component. */ void cost_qual_eval(QualCost *cost, List *quals, PlannerInfo *root) { cost_qual_eval_context context; ListCell *l; context.root = root; context.total.startup = 0; context.total.per_tuple = 0; /* We don't charge any cost for the implicit ANDing at top level ... */ foreach(l, quals) { Node *qual = (Node *) lfirst(l); cost_qual_eval_walker(qual, &context); } *cost = context.total; } //---------------------------------------------- set_rel_width /* * set_rel_width * Set the estimated output width of a base relation. * * The estimated output width is the sum of the per-attribute width estimates * for the actually-referenced columns, plus any PHVs or other expressions * that have to be calculated at this relation. This is the amount of data * we'd need to pass upwards in case of a sort, hash, etc. * * This function also sets reltarget->cost, so it's a bit misnamed now. * * NB: this works best on plain relations because it prefers to look at * real Vars. For subqueries, set_subquery_size_estimates will already have * copied up whatever per-column estimates were made within the subquery, * and for other types of rels there isn't much we can do anyway. We fall * back on (fairly stupid) datatype-based width estimates if we can't get * any better number. * * The per-attribute width estimates are cached for possible re-use while * building join relations or post-scan/join pathtargets. */ static void set_rel_width(PlannerInfo *root, RelOptInfo *rel) { Oid reloid = planner_rt_fetch(rel->relid, root)->relid; int32 tuple_width = 0; bool have_wholerow_var = false; ListCell *lc; /* Vars are assumed to have cost zero, but other exprs do not */ rel->reltarget->cost.startup = 0; rel->reltarget->cost.per_tuple = 0; foreach(lc, rel->reltarget->exprs) { Node *node = (Node *) lfirst(lc); /* * Ordinarily, a Var in a rel's targetlist must belong to that rel; * but there are corner cases involving LATERAL references where that * isn't so. If the Var has the wrong varno, fall through to the * generic case (it doesn't seem worth the trouble to be any smarter). */ if (IsA(node, Var) && ((Var *) node)->varno == rel->relid) { Var *var = (Var *) node; int ndx; int32 item_width; Assert(var->varattno >= rel->min_attr); Assert(var->varattno <= rel->max_attr); ndx = var->varattno - rel->min_attr; /* * If it's a whole-row Var, we'll deal with it below after we have * already cached as many attr widths as possible. */ if (var->varattno == 0) { have_wholerow_var = true; continue; } /* * The width may have been cached already (especially if it's a * subquery), so don't duplicate effort. */ if (rel->attr_widths[ndx] > 0) { tuple_width += rel->attr_widths[ndx]; continue; } /* Try to get column width from statistics */ if (reloid != InvalidOid && var->varattno > 0) { item_width = get_attavgwidth(reloid, var->varattno); if (item_width > 0) { rel->attr_widths[ndx] = item_width; tuple_width += item_width; continue; } } /* * Not a plain relation, or can't find statistics for it. Estimate * using just the type info. */ item_width = get_typavgwidth(var->vartype, var->vartypmod); Assert(item_width > 0); rel->attr_widths[ndx] = item_width; tuple_width += item_width; } else if (IsA(node, PlaceHolderVar)) { /* * We will need to evaluate the PHV's contained expression while * scanning this rel, so be sure to include it in reltarget->cost. */ PlaceHolderVar *phv = (PlaceHolderVar *) node; PlaceHolderInfo *phinfo = find_placeholder_info(root, phv, false); QualCost cost; tuple_width += phinfo->ph_width; cost_qual_eval_node(&cost, (Node *) phv->phexpr, root); rel->reltarget->cost.startup += cost.startup; rel->reltarget->cost.per_tuple += cost.per_tuple; } else { /* * We could be looking at an expression pulled up from a subquery, * or a ROW() representing a whole-row child Var, etc. Do what we * can using the expression type information. */ int32 item_width; QualCost cost; item_width = get_typavgwidth(exprType(node), exprTypmod(node)); Assert(item_width > 0); tuple_width += item_width; /* Not entirely clear if we need to account for cost, but do so */ cost_qual_eval_node(&cost, node, root); rel->reltarget->cost.startup += cost.startup; rel->reltarget->cost.per_tuple += cost.per_tuple; } } /* * If we have a whole-row reference, estimate its width as the sum of * per-column widths plus heap tuple header overhead. */ if (have_wholerow_var) { int32 wholerow_width = MAXALIGN(SizeofHeapTupleHeader); if (reloid != InvalidOid) { /* Real relation, so estimate true tuple width */ wholerow_width += get_relation_data_width(reloid, rel->attr_widths - rel->min_attr); } else { /* Do what we can with info for a phony rel */ AttrNumber i; for (i = 1; i <= rel->max_attr; i++) wholerow_width += rel->attr_widths[i - rel->min_attr]; } rel->attr_widths[0 - rel->min_attr] = wholerow_width; /* * Include the whole-row Var as part of the output tuple. Yes, that * really is what happens at runtime. */ tuple_width += wholerow_width; } Assert(tuple_width >= 0); rel->reltarget->width = tuple_width; }
測試腳本:
單位信息表,插入100,000行數據,dwbh為主鍵,同時創建函數索引和部分索引
drop table if exists t_dwxx; create table t_dwxx(dwmc varchar(100),dwbh varchar(20),dwdz varchar(100)); alter table t_dwxx add primary key(dwbh); create index idx_dwxx_expr on t_dwxx(trim(dwmc)); create index idx_dwxx_predicate_dwbh on t_dwxx(dwbh) where dwbh > '50000'; truncate table t_dwxx; insert into t_dwxx(dwmc,dwbh,dwdz) select 'DWMC'||generate_series(1,100000),generate_series(1,100000)||'','DWDZ'||generate_series(1,100000);
個人信息表,插入5,000,000行數據,在grbh和dwbh上創建索引
drop table if exists t_grxx; create table t_grxx(dwbh varchar(10),grbh varchar(10),xm varchar(20),xb varchar(10),nl int); insert into t_grxx(dwbh,grbh,xm,xb,nl) select generate_series(1,5000000)/50||'',generate_series(1,5000000),'XM'||generate_series(1,5000000), (case when (floor(random()*2)=0) then '男' else '女' end),floor(random() * 100 + 1)::int; create index idx_t_grxx_grbh on t_grxx(grbh); create index idx_t_dwxx_grbh on t_grxx(dwbh);
個人繳費信息表,在grbh上創建索引,多次插入5,000,000行數據
drop table if exists t_jfxx; create table t_jfxx(grbh varchar(10),ny varchar(10),je float); -- 根據實際情況,多次執行以下SQL insert into t_jfxx(grbh,ny,je) select generate_series(1,5000000), to_char(now()::timestamp - (floor(random()*240+1)||' month')::interval,'yyyymm'), floor(random()*10000+1); create index idx_t_jfxx_grbh on t_jfxx(grbh);
執行簡單的查詢SQL:
select t1.* from t_dwxx t1 where dwbh > '60000' and dwbh < '70000' and dwbh < '65000';
執行計劃如下:
testdb=# explain (analyze true,verbose) select t1.* from t_dwxx t1 where dwbh > '60000' and dwbh < '70000' and dwbh < '65000'; QUERY PLAN ----------------------------------------------------------------------------------------------------------------------------- ----------- Bitmap Heap Scan on public.t_dwxx t1 (cost=134.19..956.12 rows=5482 width=23) (actual time=1.484..2.744 rows=5554 loops=1) Output: dwmc, dwbh, dwdz Recheck Cond: (((t1.dwbh)::text > '60000'::text) AND ((t1.dwbh)::text < '70000'::text) AND ((t1.dwbh)::text < '65000'::tex t)) Heap Blocks: exact=45 -> Bitmap Index Scan on idx_dwxx_predicate_dwbh (cost=0.00..132.81 rows=5482 width=0) (actual time=1.467..1.467 rows=555 4 loops=1) Index Cond: (((t1.dwbh)::text > '60000'::text) AND ((t1.dwbh)::text < '70000'::text) AND ((t1.dwbh)::text < '65000': :text)) Planning Time: 0.204 ms Execution Time: 3.288 ms
啟動gdb跟蹤分析:
(gdb) b set_baserel_size_estimates Breakpoint 1 at 0x747bf5: file costsize.c, line 4302. (gdb) c Continuing. Breakpoint 1, set_baserel_size_estimates (root=0x2686fa8, rel=0x26873b8) at costsize.c:4302 4302 nrows = rel->tuples *
進入函數clauselist_selectivity:
(gdb) step clauselist_selectivity (root=0x2686fa8, clauses=0x271f600, varRelid=0, jointype=JOIN_INNER, sjinfo=0x0) at clausesel.c:105 105 Selectivity s1 = 1.0; ... 124 rel = find_single_rel_for_clauses(root, clauses); (gdb) 125 if (rel && rel->rtekind == RTE_RELATION && rel->statlist != NIL) #與限制條件相關的rel(t_dwxx) (gdb) p *rel $1 = {type = T_RelOptInfo, reloptkind = RELOPT_BASEREL, relids = 0x2687728, rows = 0, consider_startup = false, consider_param_startup = false, consider_parallel = true, reltarget = 0x271e228, 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 = 0x271e278, attr_widths = 0x271e308, lateral_vars = 0x0, lateral_referencers = 0x0, indexlist = 0x271e700, statlist = 0x0, pages = 726, tuples = 100000, 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 = 0x271f600, baserestrictcost = {startup = 0, per_tuple = 0}, baserestrict_min_security = 0, joininfo = 0x0, has_eclass_joins = false, top_parent_relids = 0x0, part_scheme = 0x0, nparts = 0, boundinfo = 0x0, partition_qual = 0x0, part_rels = 0x0, partexprs = 0x0, nullable_partexprs = 0x0, partitioned_child_rels = 0x0}
開始循環處理:
152 foreach(l, clauses) ...
第一個條件語句,調用clause_selectivity后,選擇率為0.44...
168 s2 = clause_selectivity(root, clause, varRelid, jointype, sjinfo); (gdb) 176 if (IsA(clause, RestrictInfo)) (gdb) p s2 $2 = 0.44045086705202319
添加到范圍條件語句中:
... 225 switch (get_oprrest(expr->opno)) (gdb) 234 addRangeClause(&rqlist, clause, (gdb) 236 break;
第二個條件語句,調用clause_selectivity后,選擇率為0.66...,,同樣會添加到范圍條件語句中:
168 s2 = clause_selectivity(root, clause, varRelid, jointype, sjinfo); (gdb) 176 if (IsA(clause, RestrictInfo)) (gdb) p s2 $3 = 0.66904390539053915 ... 225 switch (get_oprrest(expr->opno)) (gdb) 229 addRangeClause(&rqlist, clause,
第三個條件語句,調用clause_selectivity后,選擇率為0.61...,,同樣會添加到范圍條件語句中:
168 s2 = clause_selectivity(root, clause, varRelid, jointype, sjinfo); (gdb) 176 if (IsA(clause, RestrictInfo)) (gdb) p s2 $4 = 0.61437297872340435 ... 225 switch (get_oprrest(expr->opno)) (gdb) 229 addRangeClause(&rqlist, clause,
結束循環,開始處理范圍條件語句:
253 while (rqlist != NULL) (gdb) n #既有上限,也有下限 (gdb) p *rqlist $7 = {next = 0x0, var = 0x271dba8, have_lobound = true, have_hibound = true, lobound = 0.44045086705202319, hibound = 0.61437297872340435} ... #計算公式注釋已作介紹 (gdb) n 274 s2 = rqlist->hibound + rqlist->lobound - 1.0; (gdb) 277 s2 += nulltestsel(root, IS_NULL, rqlist->var, #最終結果 (gdb) 325 return s1; (gdb) p s1 $11 = 0.054823845775427538 ...
回到主函數:
(gdb) set_baserel_size_estimates (root=0x2686fa8, rel=0x26873b8) at costsize.c:4302 4302 nrows = rel->tuples * (gdb) 4309 rel->rows = clamp_row_est(nrows); (gdb) 4311 cost_qual_eval(&rel->baserestrictcost, rel->baserestrictinfo, root); (gdb) 4313 set_rel_width(root, rel); (gdb) p rel->rows $12 = 5482
結果為5482,執行計劃中的rows=5482就是這么來的.
“PostgreSQL中set_base_rel_sizes函數及其子函數案例分析”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識可以關注億速云網站,小編將為大家輸出更多高質量的實用文章!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。