statistics: global stats underestimates cardinality to a small value when "hole" exists
- Dominant language
- Go
- Stars
- 40.5k
- Forks
- 6.2k
- PR merge metrics
- PR metrics pending
Description
## Bug Report
Please answer these questions before submitting your issue. Thanks!
### 1. Minimal reproduce step (Required)
I mock the data distribution in the UT:
in partition p2: 2025-08-01 ~ 2025-08-17 data has been analyzed, 2025-08-17~2025-08-31 data not reach analyze threshold.
in partition p3: 2025-09-01 ~ 2025-09-10 data has been analyzed.
in static pruning mode the result would be right, but in dynamic pruning mode not.
```golang
func genRandDateStrs(start, end string, num int) ([]string, error) {
const layout = "2006-01-02"
// 解析日期
startDate, err := time.Parse(layout, start)
if err != nil {
return nil, fmt.Errorf("解析起始日期失败: %w", err)
}
endDate, err := time.Parse(layout, end)
if err != nil {
return nil, fmt.Errorf("解析结束日期失败: %w", err)
}
// 调整为左闭右开区间 [start, end)
endDateAdjusted := endDate.Add(-time.Nanosecond)
if startDate.After(endDateAdjusted) {
return nil, fmt.Errorf("无效日期范围: %s ≥ %s", start, end)
}
// 计算有效时间范围
duration := endDateAdjusted.Sub(startDate)
if duration < 0 {
return nil, fmt.Errorf("空日期范围: %s 到 %s", start, end)
}
// 处理非正数请求
if num <= 0 {
return []string{}, nil
}
// 初始化随机种子(只需一次)
rand.Seed(time.Now().UnixNano())
// 预分配结果切片
results := make([]string, num)
for i := 0; i < num; i++ {
// 生成随机时间差
randomDuration := time.Duration(rand.Int63n(int64(duration)))
// 计算随机日期
randomDate := startDate.Add(randomDuration)
// 格式化存储
results[i] = randomDate.Format(layout)
}
return results, nil
}
func TestOnes91742036(t *testing.T) {
store, dom := testkit.CreateMockStoreAndDomain(t)
tk := testkit.NewTestKit(t, store)
tk.MustExec("use test")
tk.MustExec(`drop table if exists t`)
tk.MustExec(`create table t(id int auto_increment, code varchar(40), time date not null,
key id_idx(id), unique key partition_date_idx(time, id))
partition by range columns (time) (
partition p1 values less than ("2025-07-01"),
partition p2 values less than ("2025-08-01"),
partition p3 values less than ("2025-09-01"),
partition p4 values less than ("2025-10-01"),
partition p5 values less than maxvalue)`)
// insert 1000 rows to initialize p3 partition
values := make([]string, 0, 1000)
dates, err := genRandDateStrs("2025-08-01", "2025-08-17", 1000)
require.NoError(t, err)
for i := 0; i < 1000; i++ {
values = append(values, fmt.Sprintf("('%s', '%s')", "code", dates[i]))
}
// insert 500 rows to initialize p4 partition
dates, err = genRandDateStrs("2025-09-01", "2025-09-10", 500)
require.NoError(t, err)
for i := 0; i < 500; i++ {
values = append(values, fmt.Sprintf("('%s', '%s')", "code", dates[i]))
}
tk.MustExec(fmt.Sprintf("insert into t(code, time) values %s", strings.Join(values, ",")))
h := dom.StatsHandle()
require.NoError(t, h.DumpStatsDeltaToKV(true))
require.NoError(t, h.Update(context.Background(), dom.InfoSchema()))
tk.MustExec("analyze table t")
// insert 200 rows, not exceed patition's modify_threshold.
values = values[:0]
dates, err = genRandDateStrs("2025-08-17", "2025-08-31", 200)
require.NoError(t, err)
for i := 0; i < 200; i++ {
values = append(values, fmt.Sprintf("('%s', '%s')", "code", dates[i]))
}
tk.MustExec(fmt.Sprintf("insert into t(code, time) values %s", strings.Join(values, ",")))
require.NoError(t, h.DumpStatsDeltaToKV(true))
require.NoError(t, h.Update(context.Background(), dom.InfoSchema()))
// id = 1 的实际行数是 20,而 time between '2025-08-23' and '2025-08-31' 的实际行数是200
// 修复前,使用的是 partition_date_idx 索引
require.False(t, dom.StatsHandle().HandleAutoAnalyze())
tk.MustQuery("explain select * from t where id = 1 and time between '2025-08-23' and '2025-08-31'").Check(testkit.Rows(
"IndexLookUp_16 1.00 root partition:p3 ",
"├─Selection_15(Build) 1.00 cop[tikv] eq(test.t.id, 1)",
"│ └─IndexRangeScan_13 3.20 cop[tikv] table:t, index:partition_date_idx(time, id) range:[2025-08-23,2025-08-31], keep order:false",
"└─TableRowIDScan_14(Probe) 1.00 cop[tikv] table:t keep order:false"))
tk.MustUseIndex("select * from t where id = 1 and time between '2025-08-23' and '2025-08-31'", "id_idx")
}
```
### 2. What did you expect to see? (Required)
use index `id_idx`
### 3. What did you see instead (Required)
use index `partition_date_idx`
### 4. What is your TiDB version? (Required)
master
Contributor guide
Assessment
This issue has not been assessed yet.