diff --git a/executor/batch_point_get.go b/executor/batch_point_get.go index 026274497e..182a757551 100644 --- a/executor/batch_point_get.go +++ b/executor/batch_point_get.go @@ -49,6 +49,8 @@ type BatchPointGetExec struct { handles []kv.Handle physIDs []int64 partPos int + singlePart bool + partTblID int64 idxVals [][]types.Datum startTS uint64 snapshotTS uint64 @@ -211,6 +213,10 @@ func (e *BatchPointGetExec) initialize(ctx context.Context) error { } physID := getPhysID(e.tblInfo, idxVals[e.partPos].GetInt64()) + // If this BatchPointGetExec is built only for the specific table partition, skip those filters not matching this partition. + if e.singlePart && e.partTblID != physID { + continue + } idxKey, err1 := EncodeUniqueIndexKey(e.ctx, e.tblInfo, e.idxInfo, idxVals, physID) if err1 != nil && !kv.ErrNotExist.Equal(err1) { return err1 @@ -326,7 +332,8 @@ func (e *BatchPointGetExec) initialize(ctx context.Context) error { sort.Slice(e.handles, less) } - keys := make([]kv.Key, len(e.handles)) + keys := make([]kv.Key, 0, len(e.handles)) + newHandles := make([]kv.Handle, 0, len(e.handles)) for i, handle := range e.handles { var tID int64 if len(e.physIDs) > 0 { @@ -342,9 +349,15 @@ func (e *BatchPointGetExec) initialize(ctx context.Context) error { tID = getPhysID(e.tblInfo, d.GetInt64()) } } + // If this BatchPointGetExec is built only for the specific table partition, skip those handles not matching this partition. + if e.singlePart && e.partTblID != tID { + continue + } key := tablecodec.EncodeRowKeyWithHandle(tID, handle) - keys[i] = key + keys = append(keys, key) + newHandles = append(newHandles, handle) } + e.handles = newHandles var values map[string][]byte // Lock keys (include exists and non-exists keys) before fetch all values for Repeatable Read Isolation. diff --git a/executor/builder.go b/executor/builder.go index 3fca9613ce..daa4a6915e 100644 --- a/executor/builder.go +++ b/executor/builder.go @@ -3901,6 +3901,8 @@ func (b *executorBuilder) buildBatchPointGet(plan *plannercore.BatchPointGetPlan lock: plan.Lock, waitTime: plan.LockWaitTime, partPos: plan.PartitionColPos, + singlePart: plan.SinglePart, + partTblID: plan.PartTblID, columns: plan.Columns, } if e.lock { diff --git a/executor/partition_table_test.go b/executor/partition_table_test.go index 404a9ffbd1..5503c486da 100644 --- a/executor/partition_table_test.go +++ b/executor/partition_table_test.go @@ -251,30 +251,3 @@ func (s *globalIndexSuite) TestIssue21731(c *C) { tk.MustExec("drop table if exists p, t") tk.MustExec("create table t (a int, b int, unique index idx(a)) partition by list columns(b) (partition p0 values in (1), partition p1 values in (2));") } - -func (s *globalIndexSuite) TestBatchPointGetTablePartition(c *C) { - testKit := testkit.NewTestKitWithInit(c, s.store) - testKit.MustExec("use test") - testKit.MustExec("drop table if exists t") - testKit.MustExec("create table t(a int, b int, primary key(a,b)) partition by hash(b) partitions 2") - testKit.MustExec("insert into t values(1,1),(1,2),(2,1),(2,2)") - - testKit.MustExec("set @@tidb_partition_prune_mode = 'static'") - testKit.MustQuery("select * from t where a in (1,2) and b = 1").Sort().Check(testkit.Rows( - "1 1", - "2 1", - )) - testKit.MustQuery("select * from t where a = 1 and b in (1,2)").Sort().Check(testkit.Rows( - "1 1", - "1 2", - )) - testKit.MustExec("set @@tidb_partition_prune_mode = 'dynamic'") - testKit.MustQuery("select * from t where a in (1,2) and b = 1").Sort().Check(testkit.Rows( - "1 1", - "2 1", - )) - testKit.MustQuery("select * from t where a = 1 and b in (1,2)").Sort().Check(testkit.Rows( - "1 1", - "1 2", - )) -} diff --git a/planner/core/cbo_test.go b/planner/core/cbo_test.go index 3215ea35da..224b10f711 100644 --- a/planner/core/cbo_test.go +++ b/planner/core/cbo_test.go @@ -996,3 +996,248 @@ func (s *testAnalyzeSuite) TestLimitIndexEstimation(c *C) { tk.MustQuery(tt).Check(testkit.Rows(output[i].Plan...)) } } + +func (s *testAnalyzeSuite) TestBatchPointGetTablePartition(c *C) { + store, dom, err := newStoreWithBootstrap() + c.Assert(err, IsNil) + testKit := testkit.NewTestKit(c, store) + defer func() { + dom.Close() + store.Close() + }() + testKit.MustExec("use test") + testKit.MustExec("drop table if exists t1,t2,t3,t4,t5,t6") + + testKit.MustExec("create table t1(a int, b int, primary key(a,b)) partition by hash(b) partitions 2") + testKit.MustExec("insert into t1 values(1,1),(1,2),(2,1),(2,2)") + testKit.MustExec("set @@tidb_partition_prune_mode = 'static'") + testKit.MustQuery("explain format = 'brief' select * from t1 where a in (1,2) and b = 1").Check(testkit.Rows( + "Batch_Point_Get 2.00 root table:t1, index:PRIMARY(a, b) keep order:false, desc:false", + )) + testKit.MustQuery("select * from t1 where a in (1,2) and b = 1").Sort().Check(testkit.Rows( + "1 1", + "2 1", + )) + testKit.MustQuery("explain format = 'brief' select * from t1 where a = 1 and b in (1,2)").Check(testkit.Rows( + "PartitionUnion 4.00 root ", + "├─Batch_Point_Get 2.00 root table:t1, index:PRIMARY(a, b) keep order:false, desc:false", + "└─Batch_Point_Get 2.00 root table:t1, index:PRIMARY(a, b) keep order:false, desc:false", + )) + testKit.MustQuery("select * from t1 where a = 1 and b in (1,2)").Sort().Check(testkit.Rows( + "1 1", + "1 2", + )) + testKit.MustExec("set @@tidb_partition_prune_mode = 'dynamic'") + testKit.MustQuery("explain format = 'brief' select * from t1 where a in (1,2) and b = 1").Check(testkit.Rows( + "IndexReader 2.00 root partition:p1 index:IndexRangeScan", + "└─IndexRangeScan 2.00 cop[tikv] table:t1, index:PRIMARY(a, b) range:[1 1,1 1], [2 1,2 1], keep order:false, stats:pseudo", + )) + testKit.MustQuery("select * from t1 where a in (1,2) and b = 1").Sort().Check(testkit.Rows( + "1 1", + "2 1", + )) + testKit.MustQuery("explain format = 'brief' select * from t1 where a = 1 and b in (1,2)").Check(testkit.Rows( + "IndexReader 2.00 root partition:p0,p1 index:IndexRangeScan", + "└─IndexRangeScan 2.00 cop[tikv] table:t1, index:PRIMARY(a, b) range:[1 1,1 1], [1 2,1 2], keep order:false, stats:pseudo", + )) + testKit.MustQuery("select * from t1 where a = 1 and b in (1,2)").Sort().Check(testkit.Rows( + "1 1", + "1 2", + )) + + testKit.MustExec("create table t2(a int, b int, primary key(a,b)) partition by range(b) (partition p0 values less than (2), partition p1 values less than maxvalue)") + testKit.MustExec("insert into t2 values(1,1),(1,2),(2,1),(2,2)") + testKit.MustExec("set @@tidb_partition_prune_mode = 'static'") + testKit.MustQuery("explain format = 'brief' select * from t2 where a in (1,2) and b = 1").Check(testkit.Rows( + "IndexReader 2.00 root index:IndexRangeScan", + "└─IndexRangeScan 2.00 cop[tikv] table:t2, partition:p0, index:PRIMARY(a, b) range:[1 1,1 1], [2 1,2 1], keep order:false, stats:pseudo", + )) + testKit.MustQuery("select * from t2 where a in (1,2) and b = 1").Sort().Check(testkit.Rows( + "1 1", + "2 1", + )) + testKit.MustQuery("explain format = 'brief' select * from t2 where a = 1 and b in (1,2)").Check(testkit.Rows( + "PartitionUnion 4.00 root ", + "├─IndexReader 2.00 root index:IndexRangeScan", + "│ └─IndexRangeScan 2.00 cop[tikv] table:t2, partition:p0, index:PRIMARY(a, b) range:[1 1,1 1], [1 2,1 2], keep order:false, stats:pseudo", + "└─IndexReader 2.00 root index:IndexRangeScan", + " └─IndexRangeScan 2.00 cop[tikv] table:t2, partition:p1, index:PRIMARY(a, b) range:[1 1,1 1], [1 2,1 2], keep order:false, stats:pseudo", + )) + testKit.MustQuery("select * from t2 where a = 1 and b in (1,2)").Sort().Check(testkit.Rows( + "1 1", + "1 2", + )) + testKit.MustExec("set @@tidb_partition_prune_mode = 'dynamic'") + testKit.MustQuery("explain format = 'brief' select * from t2 where a in (1,2) and b = 1").Check(testkit.Rows( + "IndexReader 2.00 root partition:p0 index:IndexRangeScan", + "└─IndexRangeScan 2.00 cop[tikv] table:t2, index:PRIMARY(a, b) range:[1 1,1 1], [2 1,2 1], keep order:false, stats:pseudo", + )) + testKit.MustQuery("select * from t2 where a in (1,2) and b = 1").Sort().Check(testkit.Rows( + "1 1", + "2 1", + )) + testKit.MustQuery("explain format = 'brief' select * from t2 where a = 1 and b in (1,2)").Check(testkit.Rows( + "IndexReader 2.00 root partition:all index:IndexRangeScan", + "└─IndexRangeScan 2.00 cop[tikv] table:t2, index:PRIMARY(a, b) range:[1 1,1 1], [1 2,1 2], keep order:false, stats:pseudo", + )) + testKit.MustQuery("select * from t2 where a = 1 and b in (1,2)").Sort().Check(testkit.Rows( + "1 1", + "1 2", + )) + + testKit.Se.GetSessionVars().EnableClusteredIndex = variable.ClusteredIndexDefModeOn + + testKit.MustExec("create table t3(a int, b int, primary key(a,b)) partition by hash(b) partitions 2") + testKit.MustExec("insert into t3 values(1,1),(1,2),(2,1),(2,2)") + testKit.MustExec("set @@tidb_partition_prune_mode = 'static'") + testKit.MustQuery("explain format = 'brief' select * from t3 where a in (1,2) and b = 1").Check(testkit.Rows( + "Batch_Point_Get 2.00 root table:t3, clustered index:PRIMARY(a, b) keep order:false, desc:false", + )) + testKit.MustQuery("select * from t3 where a in (1,2) and b = 1").Sort().Check(testkit.Rows( + "1 1", + "2 1", + )) + testKit.MustQuery("explain format = 'brief' select * from t3 where a = 1 and b in (1,2)").Check(testkit.Rows( + "PartitionUnion 4.00 root ", + "├─Batch_Point_Get 2.00 root table:t3, clustered index:PRIMARY(a, b) keep order:false, desc:false", + "└─Batch_Point_Get 2.00 root table:t3, clustered index:PRIMARY(a, b) keep order:false, desc:false", + )) + testKit.MustQuery("select * from t3 where a = 1 and b in (1,2)").Sort().Check(testkit.Rows( + "1 1", + "1 2", + )) + testKit.MustExec("set @@tidb_partition_prune_mode = 'dynamic'") + testKit.MustQuery("explain format = 'brief' select * from t3 where a in (1,2) and b = 1").Check(testkit.Rows( + "TableReader 2.00 root partition:p1 data:TableRangeScan", + "└─TableRangeScan 2.00 cop[tikv] table:t3 range:[1 1,1 1], [2 1,2 1], keep order:false, stats:pseudo", + )) + testKit.MustQuery("select * from t3 where a in (1,2) and b = 1").Sort().Check(testkit.Rows( + "1 1", + "2 1", + )) + testKit.MustQuery("explain format = 'brief' select * from t3 where a = 1 and b in (1,2)").Check(testkit.Rows( + "TableReader 2.00 root partition:p0,p1 data:TableRangeScan", + "└─TableRangeScan 2.00 cop[tikv] table:t3 range:[1 1,1 1], [1 2,1 2], keep order:false, stats:pseudo", + )) + testKit.MustQuery("select * from t3 where a = 1 and b in (1,2)").Sort().Check(testkit.Rows( + "1 1", + "1 2", + )) + + testKit.MustExec("create table t4(a int, b int, primary key(a,b)) partition by range(b) (partition p0 values less than (2), partition p1 values less than maxvalue)") + testKit.MustExec("insert into t4 values(1,1),(1,2),(2,1),(2,2)") + testKit.MustExec("set @@tidb_partition_prune_mode = 'static'") + testKit.MustQuery("explain format = 'brief' select * from t4 where a in (1,2) and b = 1").Check(testkit.Rows( + "TableReader 2.00 root data:TableRangeScan", + "└─TableRangeScan 2.00 cop[tikv] table:t4, partition:p0 range:[1 1,1 1], [2 1,2 1], keep order:false, stats:pseudo", + )) + testKit.MustQuery("select * from t4 where a in (1,2) and b = 1").Sort().Check(testkit.Rows( + "1 1", + "2 1", + )) + testKit.MustQuery("explain format = 'brief' select * from t4 where a = 1 and b in (1,2)").Check(testkit.Rows( + "PartitionUnion 4.00 root ", + "├─TableReader 2.00 root data:TableRangeScan", + "│ └─TableRangeScan 2.00 cop[tikv] table:t4, partition:p0 range:[1 1,1 1], [1 2,1 2], keep order:false, stats:pseudo", + "└─TableReader 2.00 root data:TableRangeScan", + " └─TableRangeScan 2.00 cop[tikv] table:t4, partition:p1 range:[1 1,1 1], [1 2,1 2], keep order:false, stats:pseudo", + )) + testKit.MustQuery("select * from t4 where a = 1 and b in (1,2)").Sort().Check(testkit.Rows( + "1 1", + "1 2", + )) + testKit.MustExec("set @@tidb_partition_prune_mode = 'dynamic'") + testKit.MustQuery("explain format = 'brief' select * from t4 where a in (1,2) and b = 1").Check(testkit.Rows( + "TableReader 2.00 root partition:p0 data:TableRangeScan", + "└─TableRangeScan 2.00 cop[tikv] table:t4 range:[1 1,1 1], [2 1,2 1], keep order:false, stats:pseudo", + )) + testKit.MustQuery("select * from t4 where a in (1,2) and b = 1").Sort().Check(testkit.Rows( + "1 1", + "2 1", + )) + testKit.MustQuery("explain format = 'brief' select * from t4 where a = 1 and b in (1,2)").Check(testkit.Rows( + "TableReader 2.00 root partition:all data:TableRangeScan", + "└─TableRangeScan 2.00 cop[tikv] table:t4 range:[1 1,1 1], [1 2,1 2], keep order:false, stats:pseudo", + )) + testKit.MustQuery("select * from t4 where a = 1 and b in (1,2)").Sort().Check(testkit.Rows( + "1 1", + "1 2", + )) + + testKit.MustExec("create table t5(a int, b int, primary key(a)) partition by hash(a) partitions 2") + testKit.MustExec("insert into t5 values(1,0),(2,0),(3,0),(4,0)") + testKit.MustExec("set @@tidb_partition_prune_mode = 'static'") + testKit.MustQuery("explain format = 'brief' select * from t5 where a in (1,2) and 1 = 1").Check(testkit.Rows( + "PartitionUnion 4.00 root ", + "├─Batch_Point_Get 2.00 root table:t5 handle:[1 2], keep order:false, desc:false", + "└─Batch_Point_Get 2.00 root table:t5 handle:[1 2], keep order:false, desc:false", + )) + testKit.MustQuery("select * from t5 where a in (1,2) and 1 = 1").Sort().Check(testkit.Rows( + "1 0", + "2 0", + )) + testKit.MustQuery("explain format = 'brief' select * from t5 where a in (1,3) and 1 = 1").Check(testkit.Rows( + "Batch_Point_Get 2.00 root table:t5 handle:[1 3], keep order:false, desc:false", + )) + testKit.MustQuery("select * from t5 where a in (1,3) and 1 = 1").Sort().Check(testkit.Rows( + "1 0", + "3 0", + )) + testKit.MustExec("set @@tidb_partition_prune_mode = 'dynamic'") + testKit.MustQuery("explain format = 'brief' select * from t5 where a in (1,2) and 1 = 1").Check(testkit.Rows( + "TableReader 2.00 root partition:p0,p1 data:TableRangeScan", + "└─TableRangeScan 2.00 cop[tikv] table:t5 range:[1,1], [2,2], keep order:false, stats:pseudo", + )) + testKit.MustQuery("select * from t5 where a in (1,2) and 1 = 1").Sort().Check(testkit.Rows( + "1 0", + "2 0", + )) + testKit.MustQuery("explain format = 'brief' select * from t5 where a in (1,3) and 1 = 1").Check(testkit.Rows( + "TableReader 2.00 root partition:p1 data:TableRangeScan", + "└─TableRangeScan 2.00 cop[tikv] table:t5 range:[1,1], [3,3], keep order:false, stats:pseudo", + )) + testKit.MustQuery("select * from t5 where a in (1,3) and 1 = 1").Sort().Check(testkit.Rows( + "1 0", + "3 0", + )) + + testKit.MustExec("create table t6(a int, b int, primary key(a)) partition by range(a) (partition p0 values less than (3), partition p1 values less than maxvalue)") + testKit.MustExec("insert into t6 values(1,0),(2,0),(3,0),(4,0)") + testKit.MustExec("set @@tidb_partition_prune_mode = 'static'") + testKit.MustQuery("explain format = 'brief' select * from t6 where a in (1,2) and 1 = 1").Check(testkit.Rows( + "TableReader 2.00 root data:TableRangeScan", + "└─TableRangeScan 2.00 cop[tikv] table:t6, partition:p0 range:[1,1], [2,2], keep order:false, stats:pseudo", + )) + testKit.MustQuery("select * from t6 where a in (1,2) and 1 = 1").Sort().Check(testkit.Rows( + "1 0", + "2 0", + )) + testKit.MustQuery("explain format = 'brief' select * from t6 where a in (1,3) and 1 = 1").Check(testkit.Rows( + "PartitionUnion 4.00 root ", + "├─TableReader 2.00 root data:TableRangeScan", + "│ └─TableRangeScan 2.00 cop[tikv] table:t6, partition:p0 range:[1,1], [3,3], keep order:false, stats:pseudo", + "└─TableReader 2.00 root data:TableRangeScan", + " └─TableRangeScan 2.00 cop[tikv] table:t6, partition:p1 range:[1,1], [3,3], keep order:false, stats:pseudo", + )) + testKit.MustQuery("select * from t6 where a in (1,3) and 1 = 1").Sort().Check(testkit.Rows( + "1 0", + "3 0", + )) + testKit.MustExec("set @@tidb_partition_prune_mode = 'dynamic'") + testKit.MustQuery("explain format = 'brief' select * from t6 where a in (1,2) and 1 = 1").Check(testkit.Rows( + "TableReader 2.00 root partition:p0 data:TableRangeScan", + "└─TableRangeScan 2.00 cop[tikv] table:t6 range:[1,1], [2,2], keep order:false, stats:pseudo", + )) + testKit.MustQuery("select * from t6 where a in (1,2) and 1 = 1").Sort().Check(testkit.Rows( + "1 0", + "2 0", + )) + testKit.MustQuery("explain format = 'brief' select * from t6 where a in (1,3) and 1 = 1").Check(testkit.Rows( + "TableReader 2.00 root partition:all data:TableRangeScan", + "└─TableRangeScan 2.00 cop[tikv] table:t6 range:[1,1], [3,3], keep order:false, stats:pseudo", + )) + testKit.MustQuery("select * from t6 where a in (1,3) and 1 = 1").Sort().Check(testkit.Rows( + "1 0", + "3 0", + )) +} diff --git a/planner/core/find_best_task.go b/planner/core/find_best_task.go index 96b75905c3..cbc233d7ff 100644 --- a/planner/core/find_best_task.go +++ b/planner/core/find_best_task.go @@ -694,21 +694,34 @@ func (ds *DataSource) findBestTask(prop *property.PhysicalProperty, planCounter p: dual, }, cntPlan, nil } - canConvertPointGet := (!ds.isPartition && len(path.Ranges) > 0) || (ds.isPartition && len(path.Ranges) == 1) - canConvertPointGet = canConvertPointGet && candidate.path.StoreType != kv.TiFlash - if !candidate.path.IsIntHandlePath { - canConvertPointGet = canConvertPointGet && - candidate.path.Index.Unique && !candidate.path.Index.HasPrefixIndex() - idxColsLen := len(candidate.path.Index.Columns) - for _, ran := range candidate.path.Ranges { + canConvertPointGet := len(path.Ranges) > 0 && path.StoreType != kv.TiFlash + if canConvertPointGet && !path.IsIntHandlePath { + // We simply do not build [batch] point get for prefix indexes. This can be optimized. + canConvertPointGet = path.Index.Unique && !path.Index.HasPrefixIndex() + // If any range cannot cover all columns of the index, we cannot build [batch] point get. + idxColsLen := len(path.Index.Columns) + for _, ran := range path.Ranges { if len(ran.LowVal) != idxColsLen { canConvertPointGet = false break } } } - if ds.table.Meta().GetPartitionInfo() != nil && ds.ctx.GetSessionVars().UseDynamicPartitionPrune() { - canConvertPointGet = false + var hashPartColName *ast.ColumnName + if tblInfo := ds.table.Meta(); canConvertPointGet && tblInfo.GetPartitionInfo() != nil { + // We do not build [batch] point get for dynamic table partitions now. This can be optimized. + if ds.ctx.GetSessionVars().UseDynamicPartitionPrune() { + canConvertPointGet = false + } else if len(path.Ranges) > 1 { + // We can only build batch point get for hash partitions on a simple column now. This is + // decided by the current implementation of `BatchPointGetExec::initialize()`, specifically, + // the `getPhysID()` function. Once we optimize that part, we can come back and enable + // BatchPointGet plan for more cases. + hashPartColName = getHashPartitionColumnName(ds.ctx, tblInfo) + if hashPartColName == nil { + canConvertPointGet = false + } + } } if canConvertPointGet { allRangeIsPoint := true @@ -723,7 +736,7 @@ func (ds *DataSource) findBestTask(prop *property.PhysicalProperty, planCounter if len(path.Ranges) == 1 { pointGetTask = ds.convertToPointGet(prop, candidate) } else { - pointGetTask = ds.convertToBatchPointGet(prop, candidate) + pointGetTask = ds.convertToBatchPointGet(prop, candidate, hashPartColName) } if !pointGetTask.invalid() { cntPlan += 1 @@ -1642,7 +1655,7 @@ func (ds *DataSource) convertToPointGet(prop *property.PhysicalProperty, candida return rTsk } -func (ds *DataSource) convertToBatchPointGet(prop *property.PhysicalProperty, candidate *candidatePath) task { +func (ds *DataSource) convertToBatchPointGet(prop *property.PhysicalProperty, candidate *candidatePath, hashPartColName *ast.ColumnName) task { if !prop.IsEmpty() && !candidate.isMatchProp { return invalidTask } @@ -1658,6 +1671,8 @@ func (ds *DataSource) convertToBatchPointGet(prop *property.PhysicalProperty, ca TblInfo: ds.TableInfo(), KeepOrder: !prop.IsEmpty(), Columns: ds.Columns, + SinglePart: ds.isPartition, + PartTblID: ds.physicalTableID, }.Init(ds.ctx, ds.tableStats.ScaleByExpectCnt(accessCnt), ds.schema.Clone(), ds.names, ds.blockOffset) if batchPointGetPlan.KeepOrder { batchPointGetPlan.Desc = prop.SortItems[0].Desc @@ -1683,6 +1698,7 @@ func (ds *DataSource) convertToBatchPointGet(prop *property.PhysicalProperty, ca batchPointGetPlan.IndexInfo = candidate.path.Index batchPointGetPlan.IdxCols = candidate.path.IdxCols batchPointGetPlan.IdxColLens = candidate.path.IdxColLens + batchPointGetPlan.PartitionColPos = getPartitionColumnPos(candidate.path.Index, hashPartColName) for _, ran := range candidate.path.Ranges { batchPointGetPlan.IndexValues = append(batchPointGetPlan.IndexValues, ran.LowVal) } diff --git a/planner/core/point_get_plan.go b/planner/core/point_get_plan.go index 4219687f91..20403d752a 100644 --- a/planner/core/point_get_plan.go +++ b/planner/core/point_get_plan.go @@ -262,6 +262,13 @@ type BatchPointGetPlan struct { Lock bool LockWaitTime int64 Columns []*model.ColumnInfo + // SinglePart indicates whether this BatchPointGetPlan is just for a single partition, instead of the whole partition table. + // If the BatchPointGetPlan is built in fast path, this value if false; if the plan is generated in physical optimization for a partition, + // this value would be true. This value would decide the behavior of BatchPointGetExec, i.e, whether to compute the table ID of the partition + // on the fly. + SinglePart bool + // PartTblID is the table ID for the specific table partition. + PartTblID int64 } // Clone implements PhysicalPlan interface. diff --git a/planner/core/point_get_plan_test.go b/planner/core/point_get_plan_test.go index fd4b0e64a5..dfece394a5 100644 --- a/planner/core/point_get_plan_test.go +++ b/planner/core/point_get_plan_test.go @@ -354,6 +354,224 @@ func (s *testPointGetSuite) TestCBOPointGet(c *C) { } } +func (s *testPointGetSuite) TestPartitionBatchPointGetPlanCache(c *C) { + testKit := testkit.NewTestKit(c, s.store) + orgEnable := core.PreparedPlanCacheEnabled() + defer func() { + core.SetPreparedPlanCache(orgEnable) + }() + core.SetPreparedPlanCache(true) + + var err error + testKit.Se, err = session.CreateSession4TestWithOpt(s.store, &session.Opt{ + PreparedPlanCache: kvcache.NewSimpleLRUCache(100, 0.1, math.MaxUint64), + }) + c.Assert(err, IsNil) + + testKit.MustExec("use test") + testKit.MustExec("drop table if exists t") + testKit.MustExec("create table t(a int, b int, unique key(a))") + testKit.MustExec("insert into t values(1,1),(2,2),(3,3)") + testKit.MustExec("prepare stmt from 'select * from t use index(a) where (a >= ? and a <= ?) or a = 3'") + testKit.MustExec("set @p=1,@q=2,@u=3") + testKit.MustQuery("execute stmt using @p,@p").Sort().Check(testkit.Rows( + "1 1", + "3 3", + )) + testKit.MustQuery("execute stmt using @u,@q").Sort().Check(testkit.Rows( + "3 3", + )) + + testKit.MustExec("drop table t") + testKit.MustExec("create table t(a int, b int, primary key(a,b)) partition by hash(b) partitions 2") + testKit.MustExec("insert into t values(1,1),(1,2),(1,3),(2,1),(2,2),(2,3),(3,1),(3,2),(3,3)") + testKit.MustExec("set @@tidb_partition_prune_mode = 'static'") + testKit.MustExec("prepare stmt from 'select * from t where ((a >= ? and a <= ?) or a = 2) and b = ?'") + testKit.MustQuery("execute stmt using @p,@p,@p").Sort().Check(testkit.Rows( + "1 1", + "2 1", + )) + testKit.MustQuery("execute stmt using @q,@q,@p").Sort().Check(testkit.Rows( + "2 1", + )) + testKit.MustQuery("execute stmt using @q,@q,@q").Sort().Check(testkit.Rows( + "2 2", + )) + testKit.MustQuery("execute stmt using @p,@u,@p").Sort().Check(testkit.Rows( + "1 1", + "2 1", + "3 1", + )) + testKit.MustQuery("execute stmt using @u,@p,@p").Sort().Check(testkit.Rows( + "2 1", + )) + + testKit.MustExec("prepare stmt from 'select * from t where a in (?,?) and b = ?'") + testKit.MustQuery("execute stmt using @p,@q,@p").Sort().Check(testkit.Rows( + "1 1", + "2 1", + )) + testKit.MustQuery("execute stmt using @q,@p,@p").Sort().Check(testkit.Rows( + "1 1", + "2 1", + )) + testKit.MustQuery("execute stmt using @q,@q,@p").Sort().Check(testkit.Rows( + "2 1", + )) + testKit.MustQuery("execute stmt using @p,@q,@q").Sort().Check(testkit.Rows( + "1 2", + "2 2", + )) + + testKit.MustExec("prepare stmt from 'select * from t where a = ? and ((b >= ? and b <= ?) or b = 2)'") + testKit.MustQuery("execute stmt using @p,@p,@p").Sort().Check(testkit.Rows( + "1 1", + "1 2", + )) + testKit.MustQuery("execute stmt using @p,@q,@q").Sort().Check(testkit.Rows( + "1 2", + )) + testKit.MustQuery("execute stmt using @q,@q,@q").Sort().Check(testkit.Rows( + "2 2", + )) + testKit.MustQuery("execute stmt using @p,@p,@u").Sort().Check(testkit.Rows( + "1 1", + "1 2", + "1 3", + )) + testKit.MustQuery("execute stmt using @p,@u,@p").Sort().Check(testkit.Rows( + "1 2", + )) + + testKit.MustExec("prepare stmt from 'select * from t where a = ? and b in (?,?)'") + testKit.MustQuery("execute stmt using @p,@p,@q").Sort().Check(testkit.Rows( + "1 1", + "1 2", + )) + testKit.MustQuery("execute stmt using @p,@q,@p").Sort().Check(testkit.Rows( + "1 1", + "1 2", + )) + testKit.MustQuery("execute stmt using @p,@q,@q").Sort().Check(testkit.Rows( + "1 2", + )) + testKit.MustQuery("execute stmt using @q,@p,@q").Sort().Check(testkit.Rows( + "2 1", + "2 2", + )) + + testKit.Se.GetSessionVars().EnableClusteredIndex = variable.ClusteredIndexDefModeOn + testKit.MustExec("drop table t") + testKit.MustExec("create table t(a int, b int, primary key(a,b)) partition by hash(b) partitions 2") + testKit.MustExec("insert into t values(1,1),(1,2),(1,3),(2,1),(2,2),(2,3),(3,1),(3,2),(3,3)") + testKit.MustExec("prepare stmt from 'select * from t where ((a >= ? and a <= ?) or a = 2) and b = ?'") + testKit.MustQuery("execute stmt using @p,@p,@p").Sort().Check(testkit.Rows( + "1 1", + "2 1", + )) + testKit.MustQuery("execute stmt using @q,@q,@p").Sort().Check(testkit.Rows( + "2 1", + )) + testKit.MustQuery("execute stmt using @q,@q,@q").Sort().Check(testkit.Rows( + "2 2", + )) + testKit.MustQuery("execute stmt using @p,@u,@p").Sort().Check(testkit.Rows( + "1 1", + "2 1", + "3 1", + )) + testKit.MustQuery("execute stmt using @u,@p,@p").Sort().Check(testkit.Rows( + "2 1", + )) + + testKit.MustExec("prepare stmt from 'select * from t where a in (?,?) and b = ?'") + testKit.MustQuery("execute stmt using @p,@q,@p").Sort().Check(testkit.Rows( + "1 1", + "2 1", + )) + testKit.MustQuery("execute stmt using @q,@p,@p").Sort().Check(testkit.Rows( + "1 1", + "2 1", + )) + testKit.MustQuery("execute stmt using @q,@q,@p").Sort().Check(testkit.Rows( + "2 1", + )) + testKit.MustQuery("execute stmt using @p,@q,@q").Sort().Check(testkit.Rows( + "1 2", + "2 2", + )) + + testKit.MustExec("prepare stmt from 'select * from t where a = ? and ((b >= ? and b <= ?) or b = 2)'") + testKit.MustQuery("execute stmt using @p,@p,@p").Sort().Check(testkit.Rows( + "1 1", + "1 2", + )) + testKit.MustQuery("execute stmt using @p,@q,@q").Sort().Check(testkit.Rows( + "1 2", + )) + testKit.MustQuery("execute stmt using @q,@q,@q").Sort().Check(testkit.Rows( + "2 2", + )) + testKit.MustQuery("execute stmt using @p,@p,@u").Sort().Check(testkit.Rows( + "1 1", + "1 2", + "1 3", + )) + testKit.MustQuery("execute stmt using @p,@u,@p").Sort().Check(testkit.Rows( + "1 2", + )) + + testKit.MustExec("prepare stmt from 'select * from t where a = ? and b in (?,?)'") + testKit.MustQuery("execute stmt using @p,@p,@q").Sort().Check(testkit.Rows( + "1 1", + "1 2", + )) + testKit.MustQuery("execute stmt using @p,@q,@p").Sort().Check(testkit.Rows( + "1 1", + "1 2", + )) + testKit.MustQuery("execute stmt using @p,@q,@q").Sort().Check(testkit.Rows( + "1 2", + )) + testKit.MustQuery("execute stmt using @q,@p,@q").Sort().Check(testkit.Rows( + "2 1", + "2 2", + )) + + testKit.MustExec("drop table t") + testKit.MustExec("create table t(a int, b int, primary key(a)) partition by hash(a) partitions 2") + testKit.MustExec("insert into t values(1,0),(2,0),(3,0),(4,0)") + testKit.MustExec("prepare stmt from 'select * from t where ((a >= ? and a <= ?) or a = 2) and 1 = 1'") + testKit.MustQuery("execute stmt using @p,@p").Sort().Check(testkit.Rows( + "1 0", + "2 0", + )) + testKit.MustQuery("execute stmt using @q,@q").Sort().Check(testkit.Rows( + "2 0", + )) + testKit.MustQuery("execute stmt using @p,@u").Sort().Check(testkit.Rows( + "1 0", + "2 0", + "3 0", + )) + testKit.MustQuery("execute stmt using @u,@p").Sort().Check(testkit.Rows( + "2 0", + )) + + testKit.MustExec("prepare stmt from 'select * from t where a in (?,?) and 1 = 1'") + testKit.MustQuery("execute stmt using @p,@q").Sort().Check(testkit.Rows( + "1 0", + "2 0", + )) + testKit.MustQuery("execute stmt using @q,@p").Sort().Check(testkit.Rows( + "1 0", + "2 0", + )) + testKit.MustQuery("execute stmt using @q,@q").Sort().Check(testkit.Rows( + "2 0", + )) +} + func (s *testPointGetSuite) TestBatchPointGetPlanCache(c *C) { tk := testkit.NewTestKit(c, s.store) orgEnable := core.PreparedPlanCacheEnabled()