diff --git a/expression/column.go b/expression/column.go index 986ed8b7b66a7..1ea9c1a41595a 100644 --- a/expression/column.go +++ b/expression/column.go @@ -196,6 +196,13 @@ type Column struct { OrigName string IsHidden bool + // IsPrefix indicates whether this column is a prefix column in index. + // + // for example: + // pk(col1, col2), index(col1(10)), key: col1(10)_col1_col2 => index's col1 will be true + // pk(col1(10), col2), index(col1), key: col1_col1(10)_col2 => pk's col1 will be true + IsPrefix bool + // InOperand indicates whether this column is the inner operand of column equal condition converted // from `[not] in (subq)`. InOperand bool @@ -504,6 +511,11 @@ func ColInfo2Col(cols []*Column, col *model.ColumnInfo) *Column { func indexCol2Col(colInfos []*model.ColumnInfo, cols []*Column, col *model.IndexColumn) *Column { for i, info := range colInfos { if info.Name.L == col.Name.L { + if col.Length > 0 && info.FieldType.Flen > col.Length { + c := *cols[i] + c.IsPrefix = true + return &c + } return cols[i] } } diff --git a/planner/core/integration_test.go b/planner/core/integration_test.go index 8ef77e8eebf26..15805ff617e92 100644 --- a/planner/core/integration_test.go +++ b/planner/core/integration_test.go @@ -2125,3 +2125,39 @@ func (s *testIntegrationSuite) TestIssue27797(c *C) { result = tk.MustQuery("select col2 from IDT_HP24172 where col1 = 8388607 and col1 in (select col1 from IDT_HP24172);") result.Check(testkit.Rows("")) } + +func (s *testIntegrationSuite) TestIssues29711(c *C) { + tk := testkit.NewTestKit(c, s.store) + tk.MustExec("use test") + + tk.MustExec("drop table if exists tbl_29711") + tk.MustExec("CREATE TABLE `tbl_29711` (" + + "`col_250` text COLLATE utf8_unicode_ci NOT NULL," + + "`col_251` varchar(10) COLLATE utf8_unicode_ci NOT NULL," + + "PRIMARY KEY (`col_251`,`col_250`(1)));") + tk.MustQuery("explain " + + "select /*+ LIMIT_TO_COP() */ col_250,col_251 from tbl_29711 use index (primary) where col_251 between 'Bob' and 'David' order by col_250,col_251 limit 6;"). + Check(testkit.Rows( + "TopN_9 6.00 root test.tbl_29711.col_250:asc, test.tbl_29711.col_251:asc, offset:0, count:6", + "└─IndexLookUp_15 6.00 root ", + " ├─IndexRangeScan_12(Build) 250.00 cop[tikv] table:tbl_29711, index:PRIMARY(col_251, col_250) range:[\"Bob\",\"David\"], keep order:false, stats:pseudo", + " └─TopN_14(Probe) 6.00 cop[tikv] test.tbl_29711.col_250:asc, test.tbl_29711.col_251:asc, offset:0, count:6", + " └─TableRowIDScan_13 250.00 cop[tikv] table:tbl_29711 keep order:false, stats:pseudo", + )) + + tk.MustExec("drop table if exists t29711") + tk.MustExec("CREATE TABLE `t29711` (" + + "`a` varchar(10) DEFAULT NULL," + + "`b` int(11) DEFAULT NULL," + + "`c` int(11) DEFAULT NULL," + + "KEY `ia` (`a`(2)))") + tk.MustQuery("explain select /*+ LIMIT_TO_COP() */ * from t29711 use index (ia) order by a limit 10;"). + Check(testkit.Rows( + "TopN_8 10.00 root test.t29711.a:asc, offset:0, count:10", + "└─IndexLookUp_14 10.00 root ", + " ├─IndexFullScan_11(Build) 10000.00 cop[tikv] table:t29711, index:ia(a) keep order:false, stats:pseudo", + " └─TopN_13(Probe) 10.00 cop[tikv] test.t29711.a:asc, offset:0, count:10", + " └─TableRowIDScan_12 10000.00 cop[tikv] table:t29711 keep order:false, stats:pseudo", + )) + +} diff --git a/planner/core/task.go b/planner/core/task.go index 3789a9f00dd50..0386c253d765d 100644 --- a/planner/core/task.go +++ b/planner/core/task.go @@ -955,14 +955,36 @@ func (p *PhysicalTopN) getPushedDownTopN(childPlan PhysicalPlan) *PhysicalTopN { return topN } +// canPushToIndexPlan checks if this TopN can be pushed to the index side of copTask. +// It can be pushed to the index side when all columns used by ByItems are available from the index side and +// there's no prefix index column. +func (p *PhysicalTopN) canPushToIndexPlan(indexPlan PhysicalPlan, byItemCols []*expression.Column) bool { + schema := indexPlan.Schema() + for _, col := range byItemCols { + pos := schema.ColumnIndex(col) + if pos == -1 { + return false + } + if schema.Columns[pos].IsPrefix { + return false + } + } + return true +} + func (p *PhysicalTopN) attach2Task(tasks ...task) task { t := tasks[0].copy() inputCount := t.count() - if copTask, ok := t.(*copTask); ok && p.canPushDown(copTask) && len(copTask.rootTaskConds) == 0 { + cols := make([]*expression.Column, 0, len(p.ByItems)) + for _, item := range p.ByItems { + cols = append(cols, expression.ExtractColumns(item.Expr)...) + } + needPushDown := len(cols) > 0 + if copTask, ok := t.(*copTask); ok && needPushDown && p.canPushDown(copTask) && len(copTask.rootTaskConds) == 0 { // If all columns in topN are from index plan, we push it to index plan, otherwise we finish the index plan and // push it to table plan. var pushedDownTopN *PhysicalTopN - if !copTask.indexPlanFinished && p.allColsFromSchema(copTask.indexPlan.Schema()) { + if !copTask.indexPlanFinished && p.canPushToIndexPlan(copTask.indexPlan, cols) { pushedDownTopN = p.getPushedDownTopN(copTask.indexPlan) copTask.indexPlan = pushedDownTopN } else {