diff --git a/pkg/planner/core/casetest/enforcempp/enforce_mpp_test.go b/pkg/planner/core/casetest/enforcempp/enforce_mpp_test.go index f6017576d0296..e21405d46a80b 100644 --- a/pkg/planner/core/casetest/enforcempp/enforce_mpp_test.go +++ b/pkg/planner/core/casetest/enforcempp/enforce_mpp_test.go @@ -41,6 +41,7 @@ func TestEnforceMPP(t *testing.T) { tk.MustExec("create table t(a int, b int)") tk.MustExec("create index idx on t(a)") tk.MustExec("CREATE TABLE `s` (\n `a` int(11) DEFAULT NULL,\n `b` int(11) DEFAULT NULL,\n `c` int(11) DEFAULT NULL,\n `d` int(11) DEFAULT NULL,\n UNIQUE KEY `a` (`a`),\n KEY `ii` (`a`,`b`)\n)") + tk.MustExec("create table t3(id int, sala char(10), name char(100), primary key(id, sala)) partition by list columns (sala)(partition p1 values in('a'));") // Default RPC encoding may cause statistics explain result differ and then the test unstable. tk.MustExec("set @@tidb_enable_chunk_rpc = on") @@ -65,6 +66,12 @@ func TestEnforceMPP(t *testing.T) { Available: true, } } + if tblInfo.Name.L == "t3" { + tblInfo.TiFlashReplica = &model.TiFlashReplicaInfo{ + Count: 1, + Available: true, + } + } } var input []string diff --git a/pkg/planner/core/casetest/enforcempp/testdata/enforce_mpp_suite_in.json b/pkg/planner/core/casetest/enforcempp/testdata/enforce_mpp_suite_in.json index 66a7e83f6456a..54e8681bd11e1 100644 --- a/pkg/planner/core/casetest/enforcempp/testdata/enforce_mpp_suite_in.json +++ b/pkg/planner/core/casetest/enforcempp/testdata/enforce_mpp_suite_in.json @@ -22,7 +22,9 @@ "explain format='verbose' select count(*) from t where a=1", "explain format='verbose' select /*+ read_from_storage(tikv[t]) */ count(*) from t where a=1", "explain format='verbose' select /*+ read_from_storage(tiflash[t]) */ count(*) from t where a=1", - "explain select /*+ READ_FROM_STORAGE(TIFLASH[s]) */ a from s where a = 10 and b is null; -- index path huristic rule will prune tiflash path" + "explain select /*+ READ_FROM_STORAGE(TIFLASH[s]) */ a from s where a = 10 and b is null; -- index path huristic rule will prune tiflash path", + "explain select /*+ read_from_storage(tiflash[t3]) */ * from t3 where sala='a' and id =1; -- once hinted, walk with tiflash range scan", + "explain select * from t3 where sala='a' and id =1; -- once not hinted, walk with tikv point get" ] }, { diff --git a/pkg/planner/core/casetest/enforcempp/testdata/enforce_mpp_suite_out.json b/pkg/planner/core/casetest/enforcempp/testdata/enforce_mpp_suite_out.json index 7dc03ab5a1154..ca8efc69e23bb 100644 --- a/pkg/planner/core/casetest/enforcempp/testdata/enforce_mpp_suite_out.json +++ b/pkg/planner/core/casetest/enforcempp/testdata/enforce_mpp_suite_out.json @@ -177,13 +177,33 @@ { "SQL": "explain select /*+ READ_FROM_STORAGE(TIFLASH[s]) */ a from s where a = 10 and b is null; -- index path huristic rule will prune tiflash path", "Plan": [ - "TableReader_12 0.10 root MppVersion: 2, data:ExchangeSender_11", - "└─ExchangeSender_11 0.10 mpp[tiflash] ExchangeType: PassThrough", + "TableReader_9 0.10 root MppVersion: 2, data:ExchangeSender_8", + "└─ExchangeSender_8 0.10 mpp[tiflash] ExchangeType: PassThrough", " └─Projection_5 0.10 mpp[tiflash] test.s.a", - " └─Selection_10 0.10 mpp[tiflash] isnull(test.s.b)", - " └─TableFullScan_9 10.00 mpp[tiflash] table:s pushed down filter:eq(test.s.a, 10), keep order:false, stats:pseudo" + " └─Selection_7 0.10 mpp[tiflash] isnull(test.s.b)", + " └─TableFullScan_6 10.00 mpp[tiflash] table:s pushed down filter:eq(test.s.a, 10), keep order:false, stats:pseudo" ], "Warn": null + }, + { + "SQL": "explain select /*+ read_from_storage(tiflash[t3]) */ * from t3 where sala='a' and id =1; -- once hinted, walk with tiflash range scan", + "Plan": [ + "TableReader_12 0.01 root MppVersion: 2, data:ExchangeSender_11", + "└─ExchangeSender_11 0.01 mpp[tiflash] ExchangeType: PassThrough", + " └─TableRangeScan_10 1.00 mpp[tiflash] table:t3, partition:p1 range:[1 \"a\",1 \"a\"], keep order:false, stats:pseudo" + ], + "Warn": [ + "disable dynamic pruning due to t3 has no global stats" + ] + }, + { + "SQL": "explain select * from t3 where sala='a' and id =1; -- once not hinted, walk with tikv point get", + "Plan": [ + "Point_Get_6 1.00 root table:t3, partition:p1, clustered index:PRIMARY(id, sala) " + ], + "Warn": [ + "disable dynamic pruning due to t3 has no global stats" + ] } ] }, diff --git a/pkg/planner/core/stats.go b/pkg/planner/core/stats.go index f2f928cd712a8..8d98a2b5efdff 100644 --- a/pkg/planner/core/stats.go +++ b/pkg/planner/core/stats.go @@ -307,6 +307,22 @@ func (ds *DataSource) derivePathStatsAndTryHeuristics() error { selected, uniqueBest, refinedBest *util.AccessPath isRefinedPath bool ) + // step1: if user prefer tiFlash store type, tiFlash path should always be built anyway ahead. + var tiflashPath *util.AccessPath + if ds.preferStoreType&preferTiFlash != 0 { + for _, path := range ds.possibleAccessPaths { + if path.StoreType == kv.TiFlash { + err := ds.deriveTablePathStats(path, ds.pushedDownConds, false) + if err != nil { + return err + } + path.IsSingleScan = true + tiflashPath = path + break + } + } + } + // step2: kv path should follow the heuristic rules. for _, path := range ds.possibleAccessPaths { if path.IsTablePath() { err := ds.deriveTablePathStats(path, ds.pushedDownConds, false) @@ -318,7 +334,9 @@ func (ds *DataSource) derivePathStatsAndTryHeuristics() error { ds.deriveIndexPathStats(path, ds.pushedDownConds, false) path.IsSingleScan = ds.isSingleScan(path.FullIdxCols, path.FullIdxColLens) } + // step: 3 // Try some heuristic rules to select access path. + // tiFlash path also have table-range-scan (range point like here) to be heuristic treated. if len(path.Ranges) == 0 { selected = path break @@ -381,13 +399,15 @@ func (ds *DataSource) derivePathStatsAndTryHeuristics() error { // heuristic rule pruning other path should consider hint prefer. // If no hints and some path matches a heuristic rule, just remove other possible paths. if selected != nil { - // if user wanna tiFlash read, while current heuristic choose a TiKV path. so we shouldn't prune other paths. + ds.possibleAccessPaths[0] = selected + ds.possibleAccessPaths = ds.possibleAccessPaths[:1] + // if user wanna tiFlash read, while current heuristic choose a TiKV path. so we shouldn't prune tiFlash path. keep := ds.preferStoreType&preferTiFlash != 0 && selected.StoreType != kv.TiFlash if keep { + // also keep tiflash path as well. + ds.possibleAccessPaths = append(ds.possibleAccessPaths, tiflashPath) return nil } - ds.possibleAccessPaths[0] = selected - ds.possibleAccessPaths = ds.possibleAccessPaths[:1] var tableName string if ds.TableAsName.O == "" { tableName = ds.tableInfo.Name.O