diff --git a/cmd/importer/parser.go b/cmd/importer/parser.go index 43f2326cbef56..a1b0d4f2a48a3 100644 --- a/cmd/importer/parser.go +++ b/cmd/importer/parser.go @@ -281,7 +281,7 @@ func parseIndex(table *table, stmt *ast.CreateIndexStmt) error { table.uniqIndices[name] = table.findCol(table.columns, name) } else if stmt.KeyType == ast.IndexKeyTypeNone { table.indices[name] = table.findCol(table.columns, name) - } else{ + } else { return errors.Errorf("unsupported index type on column %s.%s", table.name, name) } } diff --git a/planner/core/logical_plan_builder.go b/planner/core/logical_plan_builder.go index 0db47d5aeb639..411b7da332119 100644 --- a/planner/core/logical_plan_builder.go +++ b/planner/core/logical_plan_builder.go @@ -64,6 +64,8 @@ const ( HintHashAgg = "hash_agg" // HintStreamAgg is hint enforce stream aggregation. HintStreamAgg = "stream_agg" + // HintIndex is hint enforce using some indexes. + HintIndex = "index" ) const ( @@ -1949,29 +1951,44 @@ func (b *PlanBuilder) unfoldWildStar(p LogicalPlan, selectFields []*ast.SelectFi } func (b *PlanBuilder) pushTableHints(hints []*ast.TableOptimizerHint) bool { - var sortMergeTables, INLJTables, hashJoinTables []hintTableInfo - var preferAggType uint + var ( + sortMergeTables, INLJTables, hashJoinTables []hintTableInfo + indexHintList []indexHintInfo + preferAggType uint + ) for _, hint := range hints { switch hint.HintName.L { case TiDBMergeJoin, HintSMJ: - sortMergeTables = tableNames2HintTableInfo(hint.Tables) + sortMergeTables = append(sortMergeTables, tableNames2HintTableInfo(hint.Tables)...) case TiDBIndexNestedLoopJoin, HintINLJ: - INLJTables = tableNames2HintTableInfo(hint.Tables) + INLJTables = append(INLJTables, tableNames2HintTableInfo(hint.Tables)...) case TiDBHashJoin, HintHJ: - hashJoinTables = tableNames2HintTableInfo(hint.Tables) + hashJoinTables = append(hashJoinTables, tableNames2HintTableInfo(hint.Tables)...) case HintHashAgg: preferAggType |= preferHashAgg case HintStreamAgg: preferAggType |= preferStreamAgg + case HintIndex: + if len(hint.Tables) != 0 && len(hint.Indexes) != 0 { + indexHintList = append(indexHintList, indexHintInfo{ + tblName: hint.Tables[0].TableName, + indexHint: &ast.IndexHint{ + IndexNames: hint.Indexes, + HintType: ast.HintUse, + HintScope: ast.HintForScan, + }, + }) + } default: // ignore hints that not implemented } } - if len(sortMergeTables)+len(INLJTables)+len(hashJoinTables) > 0 || preferAggType != 0 { + if len(sortMergeTables)+len(INLJTables)+len(hashJoinTables)+len(indexHintList) > 0 || preferAggType != 0 { b.tableHintInfo = append(b.tableHintInfo, tableHintInfo{ sortMergeJoinTables: sortMergeTables, indexNestedLoopJoinTables: INLJTables, hashJoinTables: hashJoinTables, + indexHintList: indexHintList, preferAggType: preferAggType, }) return true @@ -2260,7 +2277,11 @@ func (b *PlanBuilder) buildDataSource(ctx context.Context, tn *ast.TableName, as return nil, ErrPartitionClauseOnNonpartitioned } - possiblePaths, err := getPossibleAccessPaths(tn.IndexHints, tableInfo) + tblName := *asName + if tblName.L == "" { + tblName = tn.Name + } + possiblePaths, err := b.getPossibleAccessPaths(tn.IndexHints, tableInfo, tblName) if err != nil { return nil, err } diff --git a/planner/core/physical_plan_test.go b/planner/core/physical_plan_test.go index a565330b64810..d8b3ca5ae729d 100644 --- a/planner/core/physical_plan_test.go +++ b/planner/core/physical_plan_test.go @@ -1703,3 +1703,75 @@ func (s *testPlanSuite) TestHintAlias(c *C) { c.Assert(core.ToString(p1), Equals, core.ToString(p2)) } } + +func (s *testPlanSuite) TestIndexHint(c *C) { + defer testleak.AfterTest(c)() + store, dom, err := newStoreWithBootstrap() + c.Assert(err, IsNil) + defer func() { + dom.Close() + store.Close() + }() + se, err := session.CreateSession4Test(store) + c.Assert(err, IsNil) + _, err = se.Execute(context.Background(), "use test") + c.Assert(err, IsNil) + + tests := []struct { + sql string + best string + hasWarn bool + }{ + // simple case + { + sql: "select /*+ INDEX(t, c_d_e) */ * from t", + best: "IndexLookUp(Index(t.c_d_e)[[NULL,+inf]], Table(t))", + hasWarn: false, + }, + { + sql: "select /*+ INDEX(t, c_d_e) */ * from t t1", + best: "TableReader(Table(t))", + hasWarn: false, + }, + { + sql: "select /*+ INDEX(t1, c_d_e) */ * from t t1", + best: "IndexLookUp(Index(t.c_d_e)[[NULL,+inf]], Table(t))", + hasWarn: false, + }, + { + sql: "select /*+ INDEX(t1, c_d_e), INDEX(t2, f) */ * from t t1, t t2 where t1.a = t2.b", + best: "LeftHashJoin{IndexLookUp(Index(t.c_d_e)[[NULL,+inf]], Table(t))->IndexLookUp(Index(t.f)[[NULL,+inf]], Table(t))}(test.t1.a,test.t2.b)", + hasWarn: false, + }, + // test multiple indexes + { + sql: "select /*+ INDEX(t, c_d_e, f, g) */ * from t order by f", + best: "IndexLookUp(Index(t.f)[[NULL,+inf]], Table(t))", + hasWarn: false, + }, + // there will be a warning instead of error when index not exist + { + sql: "select /*+ INDEX(t, no_such_index) */ * from t", + best: "TableReader(Table(t))", + hasWarn: true, + }, + } + ctx := context.Background() + for i, test := range tests { + comment := Commentf("case:%v sql:%s", i, test) + stmt, err := s.ParseOneStmt(test.sql, "", "") + c.Assert(err, IsNil, comment) + + p, err := planner.Optimize(ctx, se, stmt, s.is) + c.Assert(err, IsNil) + c.Assert(core.ToString(p), Equals, test.best, comment) + + warnings := se.GetSessionVars().StmtCtx.GetWarnings() + if test.hasWarn { + c.Assert(warnings, HasLen, 1, comment) + } else { + c.Assert(warnings, HasLen, 0, comment) + } + + } +} diff --git a/planner/core/planbuilder.go b/planner/core/planbuilder.go index bbc6f3399a843..d59e287b82c0c 100644 --- a/planner/core/planbuilder.go +++ b/planner/core/planbuilder.go @@ -58,6 +58,7 @@ type tableHintInfo struct { indexNestedLoopJoinTables []hintTableInfo sortMergeJoinTables []hintTableInfo hashJoinTables []hintTableInfo + indexHintList []indexHintInfo preferAggType uint } @@ -66,6 +67,11 @@ type hintTableInfo struct { matched bool } +type indexHintInfo struct { + tblName model.CIStr + indexHint *ast.IndexHint +} + func tableNames2HintTableInfo(hintTables []ast.HintTable) []hintTableInfo { if len(hintTables) == 0 { return nil @@ -519,7 +525,7 @@ func isPrimaryIndex(indexName model.CIStr) bool { return indexName.L == "primary" } -func getPossibleAccessPaths(indexHints []*ast.IndexHint, tblInfo *model.TableInfo) ([]*accessPath, error) { +func (b *PlanBuilder) getPossibleAccessPaths(indexHints []*ast.IndexHint, tblInfo *model.TableInfo, tblName model.CIStr) ([]*accessPath, error) { publicPaths := make([]*accessPath, 0, len(tblInfo.Indices)+1) publicPaths = append(publicPaths, &accessPath{isTablePath: true}) for _, index := range tblInfo.Indices { @@ -531,7 +537,18 @@ func getPossibleAccessPaths(indexHints []*ast.IndexHint, tblInfo *model.TableInf hasScanHint, hasUseOrForce := false, false available := make([]*accessPath, 0, len(publicPaths)) ignored := make([]*accessPath, 0, len(publicPaths)) - for _, hint := range indexHints { + + // Extract comment-style index hint like /*+ INDEX(t, idx1, idx2) */. + indexHintsLen := len(indexHints) + if hints := b.TableHints(); hints != nil { + for _, hint := range hints.indexHintList { + if hint.tblName == tblName { + indexHints = append(indexHints, hint.indexHint) + } + } + } + + for i, hint := range indexHints { if hint.HintScope != ast.HintForScan { continue } @@ -540,7 +557,13 @@ func getPossibleAccessPaths(indexHints []*ast.IndexHint, tblInfo *model.TableInf for _, idxName := range hint.IndexNames { path := getPathByIndexName(publicPaths, idxName, tblInfo) if path == nil { - return nil, ErrKeyDoesNotExist.GenWithStackByArgs(idxName, tblInfo.Name) + err := ErrKeyDoesNotExist.GenWithStackByArgs(idxName, tblInfo.Name) + // if hint is from comment-style sql hints, we should throw a warning instead of error. + if i < indexHintsLen { + return nil, err + } + b.ctx.GetSessionVars().StmtCtx.AppendWarning(err) + continue } if hint.HintType == ast.HintIgnore { // Collect all the ignored index hints.