Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

planner: add index optimizer hint #11746

Merged
merged 12 commits into from
Aug 23, 2019
2 changes: 1 addition & 1 deletion cmd/importer/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
Expand Down
35 changes: 28 additions & 7 deletions planner/core/logical_plan_builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
}
Expand Down
72 changes: 72 additions & 0 deletions planner/core/physical_plan_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}

}
}
29 changes: 26 additions & 3 deletions planner/core/planbuilder.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ type tableHintInfo struct {
indexNestedLoopJoinTables []hintTableInfo
sortMergeJoinTables []hintTableInfo
hashJoinTables []hintTableInfo
indexHintList []indexHintInfo
preferAggType uint
}

Expand All @@ -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
Expand Down Expand Up @@ -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 {
Expand All @@ -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
}
Expand All @@ -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
}
foreyes marked this conversation as resolved.
Show resolved Hide resolved
b.ctx.GetSessionVars().StmtCtx.AppendWarning(err)
continue
}
if hint.HintType == ast.HintIgnore {
// Collect all the ignored index hints.
Expand Down