diff --git a/executor/analyzetest/analyze_test.go b/executor/analyzetest/analyze_test.go index 55f3ad9397be9..2d520703e07d5 100644 --- a/executor/analyzetest/analyze_test.go +++ b/executor/analyzetest/analyze_test.go @@ -2833,7 +2833,7 @@ PARTITION BY RANGE ( a ) ( tk.MustQuery("select * from t where a > 1 and b > 1 and c > 1 and d > 1") require.NoError(t, h.LoadNeededHistograms()) tbl := h.GetTableStats(tableInfo) - require.Equal(t, 4, len(tbl.Columns)) + require.Equal(t, 0, len(tbl.Columns)) // ignore both p0's 3 buckets, persisted-partition-options' 1 bucket, just use table-level 2 buckets tk.MustExec("analyze table t partition p0") diff --git a/planner/core/collect_column_stats_usage_test.go b/planner/core/collect_column_stats_usage_test.go index c6f8cd6933f59..249f210050c56 100644 --- a/planner/core/collect_column_stats_usage_test.go +++ b/planner/core/collect_column_stats_usage_test.go @@ -331,6 +331,11 @@ func TestCollectHistNeededColumns(t *testing.T) { if len(tt.pruneMode) > 0 { s.ctx.GetSessionVars().PartitionPruneMode.Store(tt.pruneMode) } + if s.ctx.GetSessionVars().IsDynamicPartitionPruneEnabled() { + s.ctx.GetSessionVars().StmtCtx.UseDynamicPruneMode = true + } else { + s.ctx.GetSessionVars().StmtCtx.UseDynamicPruneMode = false + } stmt, err := s.p.ParseOneStmt(tt.sql, "", "") require.NoError(t, err, comment) err = Preprocess(context.Background(), s.ctx, stmt, WithPreprocessorReturn(&PreprocessorReturn{InfoSchema: s.is})) diff --git a/planner/core/integration_partition_test.go b/planner/core/integration_partition_test.go index f1b915b66d038..9df4c962541ee 100644 --- a/planner/core/integration_partition_test.go +++ b/planner/core/integration_partition_test.go @@ -1619,3 +1619,39 @@ func TestPartitionRangeColumnPruning(t *testing.T) { tk.MustQuery(`select * from t1 where a = 'a' AND c = 'd'`).Check(testkit.Rows("a d")) tk.MustExec(`drop table t1`) } + +func TestPartitionProcessorWithUninitializedTable(t *testing.T) { + store := testkit.CreateMockStore(t) + tk := testkit.NewTestKit(t, store) + tk.MustExec("use test") + tk.MustExec(" create table q1(a int, b int, key (a)) partition by range (a) (partition p0 values less than (10), partition p1 values less than (20));") + tk.MustExec(" create table q2(a int, b int, key (a)) partition by range (a) (partition p0 values less than (10), partition p1 values less than (20));") + + rows := [][]interface{}{ + {"HashJoin"}, + {"├─PartitionUnion(Build)"}, + {"│ ├─TableReader"}, + {"│ │ └─TableFullScan"}, + {"│ └─TableReader"}, + {"│ └─TableFullScan"}, + {"└─PartitionUnion(Probe)"}, + {" ├─TableReader"}, + {" │ └─TableFullScan"}, + {" └─TableReader"}, + {" └─TableFullScan"}, + } + tk.MustQuery("explain format=brief select * from q1,q2").CheckAt([]int{0}, rows) + + tk.MustExec("analyze table q1") + tk.MustQuery("explain format=brief select * from q1,q2").CheckAt([]int{0}, rows) + + tk.MustExec("analyze table q2") + rows = [][]interface{}{ + {"HashJoin"}, + {"├─TableReader(Build)"}, + {"│ └─TableFullScan"}, + {"└─TableReader(Probe)"}, + {" └─TableFullScan"}, + } + tk.MustQuery("explain format=brief select * from q1,q2").CheckAt([]int{0}, rows) +} diff --git a/planner/core/logical_plan_builder.go b/planner/core/logical_plan_builder.go index 592bb55f79619..df903670ab010 100644 --- a/planner/core/logical_plan_builder.go +++ b/planner/core/logical_plan_builder.go @@ -4471,30 +4471,39 @@ func (b *PlanBuilder) buildDataSource(ctx context.Context, tn *ast.TableName, as } if tableInfo.GetPartitionInfo() != nil { - h := domain.GetDomain(b.ctx).StatsHandle() - tblStats := h.GetTableStats(tableInfo) - isDynamicEnabled := b.ctx.GetSessionVars().IsDynamicPartitionPruneEnabled() - globalStatsReady := tblStats.IsInitialized() - // If dynamic partition prune isn't enabled or global stats is not ready, we won't enable dynamic prune mode in query - usePartitionProcessor := !isDynamicEnabled || !globalStatsReady - - failpoint.Inject("forceDynamicPrune", func(val failpoint.Value) { - if val.(bool) { - if isDynamicEnabled { - usePartitionProcessor = false - } - } - }) - - if usePartitionProcessor { + // If `UseDynamicPruneMode` already been false, then we don't need to check whether execute `flagPartitionProcessor` + // otherwise we need to check global stats initialized for each partition table + if !b.ctx.GetSessionVars().IsDynamicPartitionPruneEnabled() { b.optFlag = b.optFlag | flagPartitionProcessor - b.ctx.GetSessionVars().StmtCtx.UseDynamicPruneMode = false - if isDynamicEnabled { - b.ctx.GetSessionVars().StmtCtx.AppendWarning( - fmt.Errorf("disable dynamic pruning due to %s has no global stats", tableInfo.Name.String())) + } else { + if !b.ctx.GetSessionVars().StmtCtx.UseDynamicPruneMode { + b.optFlag = b.optFlag | flagPartitionProcessor + } else { + h := domain.GetDomain(b.ctx).StatsHandle() + tblStats := h.GetTableStats(tableInfo) + isDynamicEnabled := b.ctx.GetSessionVars().IsDynamicPartitionPruneEnabled() + globalStatsReady := tblStats.IsInitialized() + // If dynamic partition prune isn't enabled or global stats is not ready, we won't enable dynamic prune mode in query + usePartitionProcessor := !isDynamicEnabled || !globalStatsReady + + failpoint.Inject("forceDynamicPrune", func(val failpoint.Value) { + if val.(bool) { + if isDynamicEnabled { + usePartitionProcessor = false + } + } + }) + + if usePartitionProcessor { + b.optFlag = b.optFlag | flagPartitionProcessor + b.ctx.GetSessionVars().StmtCtx.UseDynamicPruneMode = false + if isDynamicEnabled { + b.ctx.GetSessionVars().StmtCtx.AppendWarning( + fmt.Errorf("disable dynamic pruning due to %s has no global stats", tableInfo.Name.String())) + } + } } } - pt := tbl.(table.PartitionedTable) // check partition by name. if len(tn.PartitionNames) > 0 { diff --git a/statistics/handle/handle.go b/statistics/handle/handle.go index b3a9c99298a92..ab900ffc43392 100644 --- a/statistics/handle/handle.go +++ b/statistics/handle/handle.go @@ -973,8 +973,13 @@ func (h *Handle) GetTableStats(tblInfo *model.TableInfo, opts ...TableStatsOpt) // GetPartitionStats retrieves the partition stats from cache. func (h *Handle) GetPartitionStats(tblInfo *model.TableInfo, pid int64, opts ...TableStatsOpt) *statistics.Table { - statsCache := h.statsCache.Load().(statsCache) var tbl *statistics.Table + if h == nil { + tbl = statistics.PseudoTable(tblInfo) + tbl.PhysicalID = pid + return tbl + } + statsCache := h.statsCache.Load().(statsCache) var ok bool option := &tableStatsOption{} for _, opt := range opts {