Skip to content

Commit a17d9ca

Browse files
authored
statistics: replace separate TopN merge with combined TopN+histogram merge for global stats (#68147)
ref #66220
1 parent 8b62b76 commit a17d9ca

29 files changed

Lines changed: 4104 additions & 1476 deletions

pkg/executor/analyze_test.go

Lines changed: 0 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -164,15 +164,6 @@ func TestAnalyzePartitionTableByConcurrencyInDynamic(t *testing.T) {
164164
{
165165
concurrency: "1",
166166
},
167-
{
168-
concurrency: "2",
169-
},
170-
{
171-
concurrency: "3",
172-
},
173-
{
174-
concurrency: "4",
175-
},
176167
{
177168
concurrency: "5",
178169
},
@@ -181,8 +172,6 @@ func TestAnalyzePartitionTableByConcurrencyInDynamic(t *testing.T) {
181172
for _, tc := range testcases {
182173
concurrency := tc.concurrency
183174
fmt.Println("testcase ", concurrency)
184-
tk.MustExec(fmt.Sprintf("set @@global.tidb_merge_partition_stats_concurrency=%v", concurrency))
185-
tk.MustQuery("select @@global.tidb_merge_partition_stats_concurrency").Check(testkit.Rows(concurrency))
186175
tk.MustExec(fmt.Sprintf("set @@tidb_analyze_partition_concurrency=%v", concurrency))
187176
tk.MustQuery("select @@tidb_analyze_partition_concurrency").Check(testkit.Rows(concurrency))
188177

@@ -201,30 +190,9 @@ func TestAnalyzePartitionTableByConcurrencyInDynamic(t *testing.T) {
201190
strconv.FormatInt(int64(i), 10), "500",
202191
})
203192
}
204-
testcases = []struct {
205-
concurrency string
206-
}{
207-
{
208-
concurrency: "1",
209-
},
210-
{
211-
concurrency: "2",
212-
},
213-
{
214-
concurrency: "3",
215-
},
216-
{
217-
concurrency: "4",
218-
},
219-
{
220-
concurrency: "5",
221-
},
222-
}
223193
for _, tc := range testcases {
224194
concurrency := tc.concurrency
225195
fmt.Println("testcase ", concurrency)
226-
tk.MustExec(fmt.Sprintf("set @@tidb_merge_partition_stats_concurrency=%v", concurrency))
227-
tk.MustQuery("select @@tidb_merge_partition_stats_concurrency").Check(testkit.Rows(concurrency))
228196
tk.MustExec("analyze table t")
229197
tk.MustQuery("show stats_topn where partition_name = 'global' and table_name = 't'").CheckAt([]int{5, 6}, expected)
230198
}

pkg/executor/set_test.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1620,6 +1620,16 @@ func TestSetConcurrency(t *testing.T) {
16201620
tk.MustQuery("show warnings").Check(testkit.Rows("Warning 1287 The 'tidb_index_serial_scan_concurrency' variable is deprecated. Sequential scans follow 'tidb_executor_concurrency', and index statistics collection uses 'tidb_analyze_distsql_scan_concurrency'."))
16211621
tk.MustQuery("select @@tidb_index_serial_scan_concurrency;").Check(testkit.Rows("4"))
16221622

1623+
// tidb_merge_partition_stats_concurrency is deprecated: setting to 1 is silent, other values warn, value always stays 1.
1624+
tk.MustExec("set @@tidb_merge_partition_stats_concurrency=1")
1625+
tk.MustQuery("show warnings").Check(testkit.Rows())
1626+
tk.MustQuery("select @@tidb_merge_partition_stats_concurrency").Check(testkit.Rows("1"))
1627+
tk.MustExec("set @@tidb_merge_partition_stats_concurrency=4")
1628+
tk.MustQuery("show warnings").Check(testkit.Rows("Warning 1287 tidb_merge_partition_stats_concurrency is deprecated: the merge no longer runs concurrently, so this setting has no effect. Kept for backward compatibility."))
1629+
tk.MustQuery("select @@tidb_merge_partition_stats_concurrency").Check(testkit.Rows("1"))
1630+
// Global getter is overridden too, a stale persisted non-1 value must not leak through.
1631+
tk.MustQuery("select @@global.tidb_merge_partition_stats_concurrency").Check(testkit.Rows("1"))
1632+
16231633
// test setting deprecated value unset
16241634
tk.MustExec("set @@tidb_index_lookup_concurrency=-1;")
16251635
tk.MustExec("set @@tidb_index_lookup_join_concurrency=-1;")

pkg/planner/cardinality/row_count_column.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,8 +106,14 @@ func equalRowCountOnColumn(sctx planctx.PlanContext, c *statistics.Column, val t
106106
histCnt, matched := c.Histogram.EqualRowCount(sctx, val, true)
107107
// Calculate histNDV here as it's needed for both the underrepresented check and later calculations
108108
histNDV := float64(c.Histogram.NDV - int64(c.TopN.Num()))
109+
// A bucket's upper bound is a value observed in the data, so a zero
110+
// Repeat is not a count of zero rows: it means no point frequency was
111+
// recorded for it. Merged global histograms produce such buckets when
112+
// an upper falls on a merge cut, and the sampled builder produces them
113+
// when the estimated NDV exceeds the histogram's row count. Fall
114+
// through to the uniform estimate rather than report an exact zero.
109115
// also check if this last bucket end value is underrepresented
110-
if matched && !IsLastBucketEndValueUnderrepresented(sctx,
116+
if matched && histCnt > 0 && !IsLastBucketEndValueUnderrepresented(sctx,
111117
&c.Histogram, val, histCnt, histNDV, realtimeRowCount, modifyCount) {
112118
return statistics.DefaultRowEst(histCnt), nil
113119
}

pkg/planner/cardinality/row_count_index.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -433,8 +433,11 @@ func equalRowCountOnIndex(sctx planctx.PlanContext, idx *statistics.Index, b []b
433433
histCnt, matched := idx.Histogram.EqualRowCount(sctx, val, true)
434434
// Calculate histNDV here as it's needed for both the underrepresented check and later calculations
435435
histNDV := float64(idx.Histogram.NDV - int64(idx.TopN.Num()))
436+
// A zero Repeat means no point frequency was recorded for this upper
437+
// bound, not that the value has no rows. See equalRowCount in
438+
// row_count_column.go.
436439
// also check if this last bucket end value is underrepresented
437-
if matched && !IsLastBucketEndValueUnderrepresented(sctx,
440+
if matched && histCnt > 0 && !IsLastBucketEndValueUnderrepresented(sctx,
438441
&idx.Histogram, val, histCnt, histNDV, realtimeRowCount, modifyCount) {
439442
return statistics.DefaultRowEst(histCnt)
440443
}

pkg/planner/cardinality/selectivity_test.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3013,3 +3013,37 @@ func TestUninitializedStats(t *testing.T) {
30133013
tk.MustQuery("show stats_histograms").CheckNotContain("allEvicted")
30143014
tk.MustQuery("explain analyze format = 'brief' select /*+ use_index(t1, idx_expr) */ * from t1 where (cast(json_unquote(json_extract(`c2`, _utf8mb4'$.location_id')) as char(255)) collate utf8mb4_bin) > '100' and c2 > 'abc';").CheckNotContain("unInitialized")
30153015
}
3016+
3017+
// TestEqualEstimateOnZeroRepeatBucketUpper covers equality estimation on a
3018+
// bucket upper that carries no point frequency. Merged global histograms
3019+
// produce such buckets when an upper falls on a merge cut, and the sampled
3020+
// builder produces them when the estimated NDV exceeds the histogram's row
3021+
// count. A bucket upper is a value observed in the data, so a zero Repeat
3022+
// means "not recorded" rather than "no rows", and the estimate must fall
3023+
// back to the uniform average instead of reporting an exact zero.
3024+
func TestEqualEstimateOnZeroRepeatBucketUpper(t *testing.T) {
3025+
tp := types.NewFieldType(mysql.TypeLonglong)
3026+
colInfo := &model.ColumnInfo{ID: 1, FieldType: *tp}
3027+
// 200 rows over an NDV of 100, so the uniform average is 2 per value.
3028+
hg := statistics.NewHistogram(colInfo.ID, 100, 0, 0, tp, 2, 0)
3029+
lo1, up1 := types.NewIntDatum(1), types.NewIntDatum(50)
3030+
lo2, up2 := types.NewIntDatum(51), types.NewIntDatum(100)
3031+
hg.AppendBucket(&lo1, &up1, 100, 0) // no frequency recorded for 50
3032+
hg.AppendBucket(&lo2, &up2, 200, 5) // 100 was observed 5 times
3033+
col := &statistics.Column{
3034+
Histogram: *hg,
3035+
Info: colInfo,
3036+
StatsLoadedStatus: statistics.NewStatsFullLoadStatus(),
3037+
StatsVer: 2,
3038+
}
3039+
sctx := mock.NewContext()
3040+
3041+
est, err := getColumnRowCount(sctx, col, getRange(50, 50), 200, 0, false)
3042+
require.NoError(t, err)
3043+
require.Equal(t, 2.0, est.Est,
3044+
"a zero Repeat must fall back to the uniform average, not report zero rows")
3045+
3046+
est, err = getColumnRowCount(sctx, col, getRange(100, 100), 200, 0, false)
3047+
require.NoError(t, err)
3048+
require.Equal(t, 5.0, est.Est, "an observed Repeat must still be used as is")
3049+
}

pkg/sessionctx/vardef/tidb_vars.go

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1026,7 +1026,8 @@ const (
10261026
TiDBOptUseInvisibleIndexes = "tidb_opt_use_invisible_indexes"
10271027
// TiDBAnalyzePartitionConcurrency is the number of concurrent workers to save statistics to the system tables.
10281028
TiDBAnalyzePartitionConcurrency = "tidb_analyze_partition_concurrency"
1029-
// TiDBMergePartitionStatsConcurrency indicates the concurrency when merge partition stats into global stats
1029+
// TiDBMergePartitionStatsConcurrency is deprecated. It is kept for backward compatibility
1030+
// but no longer affects behavior. Global stats always use the combined merge algorithm.
10301031
TiDBMergePartitionStatsConcurrency = "tidb_merge_partition_stats_concurrency"
10311032
// TiDBEnableAsyncMergeGlobalStats indicates whether to enable async merge global stats
10321033
TiDBEnableAsyncMergeGlobalStats = "tidb_enable_async_merge_global_stats"
@@ -1758,23 +1759,22 @@ const (
17581759
MinTiDBInstancePlanCacheMemSize = 100 * size.MB
17591760
DefTiDBInstancePlanCacheReservedPercentage = 0.1
17601761
// MaxDDLReorgBatchSize is exported for testing.
1761-
MaxDDLReorgBatchSize int32 = 10240
1762-
MinDDLReorgBatchSize int32 = 32
1763-
MinExpensiveQueryTimeThreshold uint64 = 10 // 10s
1764-
MinExpensiveTxnTimeThreshold uint64 = 60 // 60s
1765-
DefTiDBAutoBuildStatsConcurrency = DefBuildStatsConcurrency
1766-
DefTiDBSysProcScanConcurrency = DefAnalyzeDistSQLScanConcurrency
1767-
DefTiDBRcWriteCheckTs = false
1768-
DefTiDBForeignKeyChecks = true
1769-
DefTiDBForeignKeyCheckInSharedLock = false
1770-
DefTiDBOptAdvancedJoinHint = true
1771-
DefTiDBAnalyzePartitionConcurrency = 2
1772-
DefTiDBOptRangeMaxSize = 64 * int64(size.MB) // 64 MB
1773-
DefTiDBCostModelVer = 2
1774-
DefTiDBServerMemoryLimitSessMinSize = 128 << 20
1775-
DefTiDBMergePartitionStatsConcurrency = 1
1776-
DefTiDBServerMemoryLimitGCTrigger = 0.7
1777-
DefTiDBEnableGOGCTuner = true
1762+
MaxDDLReorgBatchSize int32 = 10240
1763+
MinDDLReorgBatchSize int32 = 32
1764+
MinExpensiveQueryTimeThreshold uint64 = 10 // 10s
1765+
MinExpensiveTxnTimeThreshold uint64 = 60 // 60s
1766+
DefTiDBAutoBuildStatsConcurrency = DefBuildStatsConcurrency
1767+
DefTiDBSysProcScanConcurrency = DefAnalyzeDistSQLScanConcurrency
1768+
DefTiDBRcWriteCheckTs = false
1769+
DefTiDBForeignKeyChecks = true
1770+
DefTiDBForeignKeyCheckInSharedLock = false
1771+
DefTiDBOptAdvancedJoinHint = true
1772+
DefTiDBAnalyzePartitionConcurrency = 2
1773+
DefTiDBOptRangeMaxSize = 64 * int64(size.MB) // 64 MB
1774+
DefTiDBCostModelVer = 2
1775+
DefTiDBServerMemoryLimitSessMinSize = 128 << 20
1776+
DefTiDBServerMemoryLimitGCTrigger = 0.7
1777+
DefTiDBEnableGOGCTuner = true
17781778
// DefTiDBGOGCTunerThreshold is to limit TiDBGOGCTunerThreshold.
17791779
DefTiDBGOGCTunerThreshold float64 = 0.6
17801780
DefTiDBGOGCMaxValue = 500

pkg/sessionctx/variable/session.go

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1708,8 +1708,6 @@ type SessionVars struct {
17081708

17091709
// AnalyzePartitionConcurrency indicates concurrency for partitions in Analyze
17101710
AnalyzePartitionConcurrency int
1711-
// AnalyzePartitionMergeConcurrency indicates concurrency for merging partition stats
1712-
AnalyzePartitionMergeConcurrency int
17131711

17141712
// EnableAsyncMergeGlobalStats indicates whether to enable async merge global stats
17151713
EnableAsyncMergeGlobalStats bool

pkg/sessionctx/variable/sysvar.go

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3230,11 +3230,28 @@ var defaultSysVars = []*SysVar{
32303230
},
32313231
},
32323232
{
3233-
Scope: vardef.ScopeGlobal | vardef.ScopeSession, Name: vardef.TiDBMergePartitionStatsConcurrency, Value: strconv.FormatInt(vardef.DefTiDBMergePartitionStatsConcurrency, 10), Type: vardef.TypeInt, MinValue: 1, MaxValue: vardef.MaxConfigurableConcurrency,
3234-
SetSession: func(s *SessionVars, val string) error {
3235-
s.AnalyzePartitionMergeConcurrency = TidbOptInt(val, vardef.DefTiDBMergePartitionStatsConcurrency)
3233+
Scope: vardef.ScopeGlobal | vardef.ScopeSession, Name: vardef.TiDBMergePartitionStatsConcurrency, Value: "1", Type: vardef.TypeInt, MinValue: 1, MaxValue: vardef.MaxConfigurableConcurrency,
3234+
SetSession: func(_ *SessionVars, _ string) error {
3235+
// Deprecated: do nothing.
32363236
return nil
32373237
},
3238+
// Both read paths return "1" unconditionally. Validation alone is
3239+
// not enough: session.GetGlobalSysVar() applies only
3240+
// ValidateFromType on the persisted value (skipping the
3241+
// Validation callback), so a cluster upgraded from an older
3242+
// TiDB with a non-1 value persisted in mysql.global_variables
3243+
// would otherwise read that stale value.
3244+
GetSession: func(_ *SessionVars) (string, error) { return "1", nil },
3245+
GetGlobal: func(_ context.Context, _ *SessionVars) (string, error) { return "1", nil },
3246+
Validation: func(vars *SessionVars, normalizedValue string, _ string, _ vardef.ScopeFlag) (string, error) {
3247+
if normalizedValue != "1" {
3248+
// Use errWarnDeprecatedSyntax (MySQL code 1287) for
3249+
// consistency with other deprecated sysvar warnings
3250+
// such as tidb_index_serial_scan_concurrency.
3251+
vars.StmtCtx.AppendWarning(errWarnDeprecatedSyntax.FastGen("tidb_merge_partition_stats_concurrency is deprecated: the merge no longer runs concurrently, so this setting has no effect. Kept for backward compatibility."))
3252+
}
3253+
return "1", nil
3254+
},
32383255
},
32393256
{
32403257
Scope: vardef.ScopeGlobal | vardef.ScopeSession,

pkg/statistics/BUILD.bazel

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ go_library(
5151
"//pkg/util/memory",
5252
"//pkg/util/ranger",
5353
"//pkg/util/sqlexec",
54+
"//pkg/util/sqlkiller",
5455
"@com_github_pingcap_errors//:errors",
5556
"@com_github_pingcap_failpoint//:failpoint",
5657
"@com_github_pingcap_tipb//go-tipb",
@@ -69,10 +70,13 @@ go_test(
6970
"cmsketch_test.go",
7071
"estimate_test.go",
7172
"fmsketch_test.go",
72-
"histogram_bench_test.go",
73+
"histogram_fuzz_test.go",
7374
"histogram_test.go",
7475
"integration_test.go",
7576
"main_test.go",
77+
"merge_global_cases_test.go",
78+
"merge_global_test.go",
79+
"merge_global_types_test.go",
7680
"sample_test.go",
7781
"scalar_test.go",
7882
"statistics_test.go",
@@ -81,13 +85,16 @@ go_test(
8185
data = glob(["testdata/**"]),
8286
embed = [":statistics"],
8387
flaky = True,
84-
shard_count = 45,
88+
shard_count = 50,
8589
deps = [
8690
"//pkg/config",
8791
"//pkg/meta/model",
8892
"//pkg/parser/ast",
93+
"//pkg/parser/charset",
8994
"//pkg/parser/mysql",
95+
"//pkg/planner/cardinality",
9096
"//pkg/planner/core/resolve",
97+
"//pkg/planner/planctx",
9198
"//pkg/sessionctx",
9299
"//pkg/sessionctx/stmtctx",
93100
"//pkg/sessionctx/vardef",
@@ -107,6 +114,7 @@ go_test(
107114
"//pkg/util/mock",
108115
"//pkg/util/ranger",
109116
"//pkg/util/sqlexec",
117+
"//pkg/util/sqlkiller",
110118
"@com_github_pingcap_errors//:errors",
111119
"@com_github_pingcap_failpoint//:failpoint",
112120
"@com_github_stretchr_testify//require",

pkg/statistics/cmsketch_util.go

Lines changed: 12 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -17,58 +17,24 @@ package statistics
1717
import (
1818
"time"
1919

20+
"github.com/pingcap/tidb/pkg/tablecodec"
2021
"github.com/pingcap/tidb/pkg/types"
2122
"github.com/pingcap/tidb/pkg/util/codec"
22-
"github.com/pingcap/tidb/pkg/util/hack"
2323
)
2424

25-
// DatumMapCache is used to store the mapping from the string type to datum type.
26-
// The datum is used to find the value in the histogram.
27-
type DatumMapCache struct {
28-
datumMap map[hack.MutableString]types.Datum
29-
}
30-
31-
// NewDatumMapCache creates a new DatumMapCache.
32-
func NewDatumMapCache() *DatumMapCache {
33-
return &DatumMapCache{
34-
datumMap: make(map[hack.MutableString]types.Datum),
35-
}
36-
}
37-
38-
// Get gets the datum from the cache.
39-
func (d *DatumMapCache) Get(key hack.MutableString) (val types.Datum, ok bool) {
40-
val, ok = d.datumMap[key]
41-
return
42-
}
43-
44-
// Put puts the datum into the cache.
45-
func (d *DatumMapCache) Put(val TopNMeta, encodedVal hack.MutableString,
46-
tp byte, isIndex bool, loc *time.Location) (dat types.Datum, err error) {
47-
dat, err = topNMetaToDatum(val, tp, isIndex, loc)
48-
if err != nil {
49-
return dat, err
50-
}
51-
d.datumMap[encodedVal] = dat
52-
return dat, nil
53-
}
54-
5525
func topNMetaToDatum(val TopNMeta,
56-
tp byte, isIndex bool, loc *time.Location) (dat types.Datum, err error) {
26+
ft *types.FieldType, isIndex bool, loc *time.Location) (dat types.Datum, err error) {
5727
if isIndex {
5828
dat.SetBytes(val.Encoded)
59-
} else {
60-
var err error
61-
if types.IsTypeTime(tp) {
62-
// Handle date time values specially since they are encoded to int and we'll get int values if using DecodeOne.
63-
_, dat, err = codec.DecodeAsDateTime(val.Encoded, tp, loc)
64-
} else if types.IsTypeFloat(tp) {
65-
_, dat, err = codec.DecodeAsFloat32(val.Encoded, tp)
66-
} else {
67-
_, dat, err = codec.DecodeOne(val.Encoded)
68-
}
69-
if err != nil {
70-
return dat, err
71-
}
29+
return dat, nil
30+
}
31+
if _, dat, err = codec.DecodeOne(val.Encoded); err != nil {
32+
return dat, err
7233
}
73-
return dat, err
34+
// The key encodes a value in its flattened form: ENUM, SET and BIT
35+
// as their numeric value, times as a packed integer, TypeFloat as a
36+
// float64. Unflatten restores the kind the column's own values
37+
// carry, which matters because Datum.Compare dispatches on kind and
38+
// because a histogram's chunk column is typed.
39+
return tablecodec.Unflatten(dat, ft, loc)
7440
}

0 commit comments

Comments
 (0)