From 78181015adfe1503323b57568cbfef68b1b6882c Mon Sep 17 00:00:00 2001 From: "ZizhuanLiu(X-MAN)" <44973863@qq.com> Date: Tue, 15 Sep 2026 10:01:44 +0800 Subject: [PATCH v1] Optimize MCV stats for sortable types and utilize sorted-order properties 1.Preserve ascending-ordered MCV values for sort-comparable types when filling pg_statistic. In compute_scalar_stats(), retain existing logic and allocate extra ScalarMCVItem workspace to hold sorted MCV entries. 2.When applying statistics, use the pre-sorted MCV list: -compare against min/max boundaries. Boundary hits finish in 1-2 comparisons. -Values inside MCV range use binary-search (average N/2 -> log(n)). -Values outside MCV range skip full MCV iteration (N -> at most 2 comparisons). 3.TODO: - Audit functions for benefits / regressions caused by sorted MCV and apply fixes - Compatibility support for non-sortable types and sorted-state detection --- src/backend/commands/analyze.c | 11 +- src/backend/utils/adt/selfuncs.c | 226 +++++++++++++++++++++++++++---- 2 files changed, 212 insertions(+), 25 deletions(-) diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c index c05f9f50..1ba218c3 100644 --- a/src/backend/commands/analyze.c +++ b/src/backend/commands/analyze.c @@ -2481,6 +2481,8 @@ compute_scalar_stats(VacAttrStatsP stats, int values_cnt = 0; int *tupnoLink; ScalarMCVItem *track; + ScalarMCVItem *track_sorted_values; /* tracks values sorted by + * compare_scalars() */ int track_cnt = 0; int num_mcv = stats->attstattarget; int num_bins = stats->attstattarget; @@ -2489,6 +2491,7 @@ compute_scalar_stats(VacAttrStatsP stats, values = palloc_array(ScalarItem, samplerows); tupnoLink = palloc_array(int, samplerows); track = palloc_array(ScalarMCVItem, num_mcv); + track_sorted_values = palloc_array(ScalarMCVItem, num_mcv); memset(&ssup, 0, sizeof(ssup)); ssup.ssup_cxt = CurrentMemoryContext; @@ -2633,6 +2636,9 @@ compute_scalar_stats(VacAttrStatsP stats, } track[j].count = dups_cnt; track[j].first = i + 1 - dups_cnt; + track_sorted_values[track_cnt - 1].count = dups_cnt; + track_sorted_values[track_cnt - 1].first = i + 1 - dups_cnt; + } } dups_cnt = 0; @@ -2776,10 +2782,11 @@ compute_scalar_stats(VacAttrStatsP stats, mcv_freqs = palloc_array(float4, num_mcv); for (i = 0; i < num_mcv; i++) { - mcv_values[i] = datumCopy(values[track[i].first].value, + /* copy in value order */ + mcv_values[i] = datumCopy(values[track_sorted_values[i].first].value, stats->attrtype->typbyval, stats->attrtype->typlen); - mcv_freqs[i] = (double) track[i].count / (double) samplerows; + mcv_freqs[i] = (double) track_sorted_values[i].count / (double) samplerows; } MemoryContextSwitchTo(old_context); diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c index e27ec9e5..9fb17d92 100644 --- a/src/backend/utils/adt/selfuncs.c +++ b/src/backend/utils/adt/selfuncs.c @@ -118,6 +118,7 @@ #include "optimizer/paths.h" #include "optimizer/plancat.h" #include "parser/parse_clause.h" +#include "parser/parse_oper.h" #include "parser/parse_relation.h" #include "parser/parsetree.h" #include "rewrite/rewriteManip.h" @@ -137,6 +138,7 @@ #include "utils/selfuncs.h" #include "utils/snapmgr.h" #include "utils/spccache.h" +#include "utils/sortsupport.h" #include "utils/syscache.h" #include "utils/timestamp.h" #include "utils/typcache.h" @@ -413,6 +415,7 @@ var_eq_const(VariableStatData *vardata, Oid oproid, Oid collation, AttStatsSlot sslot; bool match = false; int i; + double sumcommon = 0.0; /* * Is the constant "=" to any of the column's most common values? @@ -427,6 +430,19 @@ var_eq_const(VariableStatData *vardata, Oid oproid, Oid collation, { LOCAL_FCINFO(fcinfo, 2); FmgrInfo eqproc; + bool scan_entire_mcv = false; +/* + * Status codes for in_mcv_range: + * 0 - Need to compare against each MCV value. + * 1 - Falls within MCV range; still need to check whether present in list via binary search. + * 2 - Falls outside MCV range; no need to compare against each MCV value. + */ +#define IN_MCV_RANGE_UNKNOWN 0 +#define IN_MCV_RANGE_YES 1 +#define IN_MCV_RANGE_NO 2 + int in_mcv_range = IN_MCV_RANGE_UNKNOWN; + int nvalues = sslot.nvalues; + SortSupportData ssup = {0}; fmgr_info(opfuncoid, &eqproc); @@ -446,20 +462,195 @@ var_eq_const(VariableStatData *vardata, Oid oproid, Oid collation, else fcinfo->args[0].value = constval; - for (i = 0; i < sslot.nvalues; i++) + selec = 0.0; + i = 0; + + if (sslot.stacoll == collation) { - Datum fresult; + /* + * If the collations are equal and the MCV values can be + * sorted in ascending order, and the MCV list is already + * sorted, first check whether the constant expression falls + * within the range of MCV values: + * + * - If it is equal to the first or last MCV value, the lookup + * can be completed immediately. + * + * - If it falls within the MCV range, use binary search to find + * a matching value, reducing the average number of comparisons + * from N/2 to at most log(N). + * + * - If it falls outside the MCV range, subsequent processing can + * sum sumcommon directly without comparing against the MCV values. + * In this worst-case scenario where the constant does not match + * any MCV value, this reduces the number of comparisons from N to + * at most 2. + */ + Oid ltopr; + Oid eqopr; + + /* Look for default "<" and "=" operators for sslot.valuetype */ + get_sort_group_operators(sslot.valuetype, + false, false, false, + <opr, &eqopr, NULL, + NULL); + if (OidIsValid(eqopr) && OidIsValid(ltopr)) + { + int compare; + double number; - if (varonleft) - fcinfo->args[0].value = sslot.values[i]; - else - fcinfo->args[1].value = sslot.values[i]; - fcinfo->isnull = false; - fresult = FunctionCallInvoke(fcinfo); - if (!fcinfo->isnull && DatumGetBool(fresult)) + /* + * This patch assumes MCV list is sorted by + * value;follow‑up patches will handle compatibility + * including sorted‑state checks. + * + * Todo + */ + ssup.ssup_cxt = CurrentMemoryContext; + ssup.ssup_collation = sslot.stacoll; + ssup.ssup_nulls_first = false; + ssup.abbreviate = false; + PrepareSortSupportFromOrderingOp(ltopr, &ssup); + + /* First compare against values[0] */ + compare = ApplySortComparator(constval, false, + sslot.values[0], false, &ssup); + number = sslot.numbers[0]; + if (compare == 0) + { + /* + * Constant is "=" to this common value. We know + * selectivity exactly (or as exactly as ANALYZE could + * calculate it, anyway). + */ + match = true; + selec = number; + } + else if (compare < 0 || nvalues == 1) + { + in_mcv_range = IN_MCV_RANGE_NO; + } + else + { + /* Next compare against values[nvalues - 1] */ + nvalues--; + compare = ApplySortComparator(constval, false, + sslot.values[nvalues], false, &ssup); + number = sslot.numbers[nvalues]; + if (compare == 0) + { + /* + * Constant is "=" to this common value. We know + * selectivity exactly (or as exactly as ANALYZE + * could calculate it, anyway). + */ + match = true; + selec = number; + } + else if (compare > 0) + in_mcv_range = IN_MCV_RANGE_NO; + else + { + /* For binary search: refers to the first and last uncompared elements */ + i = 1; + nvalues--; + + in_mcv_range = IN_MCV_RANGE_YES; + } + } + } + } + else if (sslot.stacoll != collation && OidIsValid(collation)) + { + /* + * Scanning the entire MCV array is needed when the collation + * used for the comparison is nondeterministic and differs + * from the statistics collation. In this case, the comparison + * may match multiple MCV values, so we must continue scanning + * after finding a match. + */ + pg_locale_t mylocale = pg_newlocale_from_collation(collation); + + scan_entire_mcv = !mylocale->deterministic; + } + + if (!match) + { + if (in_mcv_range == IN_MCV_RANGE_YES) { - match = true; - break; + /* Binary search */ + while (i <= nvalues) + { + int compare; + int mid = (i + nvalues) / 2; + + compare = ApplySortComparator(constval, false, + sslot.values[mid], false, &ssup); + if (compare == 0) + { + /* + * Constant is "=" to this common value. We know + * selectivity exactly (or as exactly as ANALYZE + * could calculate it, anyway). + */ + match = true; + selec = sslot.numbers[mid]; + break; + } + else if (compare > 0) + i = mid + 1; + else + nvalues = mid - 1; + } + } + + if (!match) + { + /* + * Compare the constant expression with the MCVs. + * + * If the constant matches the current MCV, stop here when + * a full MCV scan is not required. Otherwise, continue + * scanning the remaining MCVs and accumulate the + * selectivity of all matching MCVs. + * + * While scanning, also accumulate the selectivity of the + * MCVs examined so far. This is used later to estimate + * the selectivity of a non-NULL constant that does not + * match any MCV. + */ + for (i = 0; i < nvalues; i++) + { + if (in_mcv_range == IN_MCV_RANGE_UNKNOWN || scan_entire_mcv) + { + Datum fresult; + + if (varonleft) + fcinfo->args[0].value = sslot.values[i]; + else + fcinfo->args[1].value = sslot.values[i]; + fcinfo->isnull = false; + fresult = FunctionCallInvoke(fcinfo); + if (!fcinfo->isnull && DatumGetBool(fresult)) + { + /* + * Constant is "=" to this common value. We + * know selectivity exactly (or as exactly as + * ANALYZE could calculate it, anyway). + */ + match = true; + if (!scan_entire_mcv) + { + selec = sslot.numbers[i]; + break; + } + + selec += sslot.numbers[i]; + } + } + + sumcommon += sslot.numbers[i]; + } } } } @@ -469,26 +660,15 @@ var_eq_const(VariableStatData *vardata, Oid oproid, Oid collation, i = 0; /* keep compiler quiet */ } - if (match) - { - /* - * Constant is "=" to this common value. We know selectivity - * exactly (or as exactly as ANALYZE could calculate it, anyway). - */ - selec = sslot.numbers[i]; - } - else + if (!match) { /* * Comparison is against a constant that is neither NULL nor any * of the common values. Its selectivity cannot be more than * this: */ - double sumcommon = 0.0; double otherdistinct; - for (i = 0; i < sslot.nnumbers; i++) - sumcommon += sslot.numbers[i]; selec = 1.0 - sumcommon - nullfrac; CLAMP_PROBABILITY(selec); -- 2.43.0