Skip to content

Commit ec781f5

Browse files
authored
bindinfo: explore per-table index hints during plan generation (#66000)
close #65999
1 parent 715b1cc commit ec781f5

File tree

3 files changed

+96
-59
lines changed

3 files changed

+96
-59
lines changed

pkg/bindinfo/binding_plan_generation.go

Lines changed: 86 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -134,12 +134,12 @@ func (gp *genedPlan) PlanText() string {
134134

135135
// state represents a state of the optimizer variables and fixes.
136136
type state struct {
137-
leading2 [2]*tableName // leading-2 table names
138-
indexHint *indexHint // optional index hint for a table (one at a time to limit search space)
139-
varNames []string // relevant variables and their values to generate a certain plan
140-
varValues []any
141-
fixIDs []uint64 // relevant fixes and their values to generate a certain plan
142-
fixValues []string
137+
leading2 [2]*tableName // leading-2 table names
138+
indexHints []*indexHint // optional index hints per table
139+
varNames []string // relevant variables and their values to generate a certain plan
140+
varValues []any
141+
fixIDs []uint64 // relevant fixes and their values to generate a certain plan
142+
fixValues []string
143143
}
144144

145145
// Encode encodes the state into a string.
@@ -154,11 +154,14 @@ func (s *state) Encode() string {
154154
}
155155
sb.WriteString(t.String())
156156
}
157-
if s.indexHint != nil {
157+
for _, indexHint := range s.indexHints {
158+
if indexHint == nil {
159+
continue
160+
}
158161
if sb.Len() > 0 {
159162
sb.WriteString(",")
160163
}
161-
sb.WriteString(s.indexHint.String())
164+
sb.WriteString(indexHint.String())
162165
}
163166
for _, v := range s.varValues {
164167
if sb.Len() > 0 {
@@ -182,35 +185,39 @@ func (s *state) Encode() string {
182185

183186
func newStateWithLeading2(old *state, leading2 [2]*tableName) *state {
184187
newState := &state{
185-
leading2: leading2,
186-
indexHint: old.indexHint,
187-
varNames: old.varNames,
188-
varValues: old.varValues,
189-
fixIDs: old.fixIDs,
190-
fixValues: old.fixValues,
188+
leading2: leading2,
189+
indexHints: append([]*indexHint(nil), old.indexHints...),
190+
varNames: old.varNames,
191+
varValues: old.varValues,
192+
fixIDs: old.fixIDs,
193+
fixValues: old.fixValues,
191194
}
192195
return newState
193196
}
194197

195-
func newStateWithIndexHint(old *state, indexHint *indexHint) *state {
198+
func newStateWithIndexHint(old *state, tableIdx int, hint *indexHint) *state {
199+
newHints := append([]*indexHint(nil), old.indexHints...)
200+
if tableIdx >= 0 && tableIdx < len(newHints) {
201+
newHints[tableIdx] = hint
202+
}
196203
return &state{
197-
leading2: old.leading2,
198-
indexHint: indexHint,
199-
varNames: old.varNames,
200-
varValues: old.varValues,
201-
fixIDs: old.fixIDs,
202-
fixValues: old.fixValues,
204+
leading2: old.leading2,
205+
indexHints: newHints,
206+
varNames: old.varNames,
207+
varValues: old.varValues,
208+
fixIDs: old.fixIDs,
209+
fixValues: old.fixValues,
203210
}
204211
}
205212

206213
func newStateWithNewVar(old *state, varName string, varVal any) *state {
207214
newState := &state{
208-
leading2: old.leading2,
209-
indexHint: old.indexHint,
210-
varNames: old.varNames,
211-
varValues: make([]any, len(old.varValues)),
212-
fixIDs: old.fixIDs,
213-
fixValues: old.fixValues,
215+
leading2: old.leading2,
216+
indexHints: append([]*indexHint(nil), old.indexHints...),
217+
varNames: old.varNames,
218+
varValues: make([]any, len(old.varValues)),
219+
fixIDs: old.fixIDs,
220+
fixValues: old.fixValues,
214221
}
215222
copy(newState.varValues, old.varValues)
216223
for i := range newState.varNames {
@@ -224,12 +231,12 @@ func newStateWithNewVar(old *state, varName string, varVal any) *state {
224231

225232
func newStateWithNewFix(old *state, fixID uint64, fixVal string) *state {
226233
newState := &state{
227-
leading2: old.leading2,
228-
indexHint: old.indexHint,
229-
varNames: old.varNames,
230-
varValues: old.varValues,
231-
fixIDs: old.fixIDs,
232-
fixValues: make([]string, len(old.fixValues)),
234+
leading2: old.leading2,
235+
indexHints: append([]*indexHint(nil), old.indexHints...),
236+
varNames: old.varNames,
237+
varValues: old.varValues,
238+
fixIDs: old.fixIDs,
239+
fixValues: make([]string, len(old.fixValues)),
233240
}
234241
copy(newState.fixValues, old.fixValues)
235242
for i := range newState.fixIDs {
@@ -263,20 +270,20 @@ func generatePlanWithSCtx(sctx sessionctx.Context, defaultSchema, sql, charset,
263270
possibleLeading2 = append(possibleLeading2, [2]*tableName{tableNames[i], tableNames[j]})
264271
}
265272
}
266-
possibleIndexHints := extractSelectIndexHints(sctx, defaultSchema, stmt)
267-
return breadthFirstPlanSearch(sctx, stmt, vars, fixes, possibleLeading2, possibleIndexHints)
273+
indexHintOptions := extractSelectIndexHints(sctx, defaultSchema, stmt)
274+
return breadthFirstPlanSearch(sctx, stmt, vars, fixes, possibleLeading2, indexHintOptions)
268275
}
269276

270277
func breadthFirstPlanSearch(sctx sessionctx.Context, stmt ast.StmtNode,
271-
vars []string, fixes []uint64, possibleLeading2 [][2]*tableName, possibleIndexHints []*indexHint) (plans []*genedPlan, err error) {
278+
vars []string, fixes []uint64, possibleLeading2 [][2]*tableName, indexHintOptions [][]*indexHint) (plans []*genedPlan, err error) {
272279
// init BFS structures
273280
visitedStates := make(map[string]struct{}) // map[encodedState]struct{}, all visited states
274281
visitedPlans := make(map[string]*genedPlan) // map[planDigest]plan, all visited plans
275282
stateList := list.New() // states in queue to explore
276283

277284
// init the start state and push it into the BFS list
278285
// start state: no specified leading hint + default values of all variables and fix-controls
279-
startState, err := getStartState(vars, fixes)
286+
startState, err := getStartState(vars, fixes, len(indexHintOptions))
280287
if err != nil {
281288
return nil, err
282289
}
@@ -300,11 +307,13 @@ func breadthFirstPlanSearch(sctx sessionctx.Context, stmt ast.StmtNode,
300307
stateList.PushBack(newState)
301308
}
302309
}
303-
for _, indexHint := range possibleIndexHints {
304-
newState := newStateWithIndexHint(currState, indexHint)
305-
if _, ok := visitedStates[newState.Encode()]; !ok {
306-
visitedStates[newState.Encode()] = struct{}{}
307-
stateList.PushBack(newState)
310+
for tableIdx := range indexHintOptions {
311+
for _, indexHint := range indexHintOptions[tableIdx] {
312+
newState := newStateWithIndexHint(currState, tableIdx, indexHint)
313+
if _, ok := visitedStates[newState.Encode()]; !ok {
314+
visitedStates[newState.Encode()] = struct{}{}
315+
stateList.PushBack(newState)
316+
}
308317
}
309318
}
310319
for i := range vars {
@@ -416,7 +425,14 @@ func genPlanUnderState(sctx sessionctx.Context, stmt ast.StmtNode, state *state)
416425
sctx.GetSessionVars().OptimizerFixControl = fixControlMap
417426

418427
if sel, isSel := stmt.(*ast.SelectStmt); isSel {
419-
if (state.leading2[0] != nil && state.leading2[1] != nil) || state.indexHint != nil {
428+
hasIndexHint := false
429+
for _, indexHint := range state.indexHints {
430+
if indexHint != nil {
431+
hasIndexHint = true
432+
break
433+
}
434+
}
435+
if (state.leading2[0] != nil && state.leading2[1] != nil) || hasIndexHint {
420436
originalHintsLen := len(sel.TableHints)
421437
defer func() {
422438
sel.TableHints = sel.TableHints[:originalHintsLen]
@@ -437,18 +453,21 @@ func genPlanUnderState(sctx sessionctx.Context, stmt ast.StmtNode, state *state)
437453
}
438454
sel.TableHints = append(sel.TableHints, leadingHint)
439455
}
440-
if state.indexHint != nil {
441-
indexHint := &ast.TableOptimizerHint{
456+
for _, indexHint := range state.indexHints {
457+
if indexHint == nil {
458+
continue
459+
}
460+
hintNode := &ast.TableOptimizerHint{
442461
HintName: ast.NewCIStr(hint.HintUseIndex),
443462
Tables: []ast.HintTable{
444463
{
445-
DBName: ast.NewCIStr(state.indexHint.table.schema),
446-
TableName: ast.NewCIStr(state.indexHint.table.HintName()),
464+
DBName: ast.NewCIStr(indexHint.table.schema),
465+
TableName: ast.NewCIStr(indexHint.table.HintName()),
447466
},
448467
},
449-
Indexes: []ast.CIStr{ast.NewCIStr(state.indexHint.index)},
468+
Indexes: []ast.CIStr{ast.NewCIStr(indexHint.index)},
450469
}
451-
sel.TableHints = append(sel.TableHints, indexHint)
470+
sel.TableHints = append(sel.TableHints, hintNode)
452471
}
453472
}
454473
}
@@ -518,9 +537,13 @@ func adjustFix(fixID uint64, fixVal string) (newFixVal string, err error) {
518537
}
519538
}
520539

521-
func getStartState(vars []string, fixes []uint64) (*state, error) {
540+
func getStartState(vars []string, fixes []uint64, indexHintCount int) (*state, error) {
522541
// use the default values of these vars and fix-controls as the initial state.
523-
s := &state{varNames: vars, fixIDs: fixes}
542+
s := &state{
543+
varNames: vars,
544+
fixIDs: fixes,
545+
indexHints: make([]*indexHint, indexHintCount),
546+
}
524547
for _, varName := range vars {
525548
switch varName {
526549
case vardef.TiDBOptIndexScanCostFactor:
@@ -755,7 +778,7 @@ func collectJoinPredicates(node ast.ResultSetNode, extractor *predicateColumnExt
755778
}
756779
}
757780

758-
func extractSelectIndexHints(sctx sessionctx.Context, defaultSchema string, node ast.StmtNode) []*indexHint {
781+
func extractSelectIndexHints(sctx sessionctx.Context, defaultSchema string, node ast.StmtNode) [][]*indexHint {
759782
selStmt, isSel := node.(*ast.SelectStmt)
760783
if !isSel {
761784
return nil
@@ -765,9 +788,10 @@ func extractSelectIndexHints(sctx sessionctx.Context, defaultSchema string, node
765788
return nil
766789
}
767790
allowAnyTable := len(tableNames) == 1
768-
hints := make([]*indexHint, 0)
769-
seen := make(map[string]struct{})
791+
hintOptions := make([][]*indexHint, 0, len(tableNames))
770792
for _, target := range tableNames {
793+
options := make([]*indexHint, 0, 4)
794+
options = append(options, nil) // empty option to avoid forcing index paths
771795
extractor := &predicateColumnExtractor{
772796
table: target,
773797
columns: make(map[string]struct{}),
@@ -780,13 +804,16 @@ func extractSelectIndexHints(sctx sessionctx.Context, defaultSchema string, node
780804
collectJoinPredicates(selStmt.From.TableRefs, extractor)
781805
}
782806
if len(extractor.columns) == 0 {
807+
hintOptions = append(hintOptions, options)
783808
continue
784809
}
785810
tblInfo, err := sctx.GetLatestInfoSchema().TableInfoByName(ast.NewCIStr(target.schema), ast.NewCIStr(target.name))
786811
if err != nil {
812+
hintOptions = append(hintOptions, options)
787813
continue
788814
}
789815
useInvisible := sctx.GetSessionVars().OptimizerUseInvisibleIndexes
816+
seen := make(map[string]struct{})
790817
for _, index := range tblInfo.Indices {
791818
if index.State != model.StatePublic {
792819
continue
@@ -810,12 +837,13 @@ func extractSelectIndexHints(sctx sessionctx.Context, defaultSchema string, node
810837
table: target,
811838
index: index.Name.O,
812839
}
813-
if _, ok := seen[hint.String()]; ok {
840+
if _, ok := seen[hint.index]; ok {
814841
continue
815842
}
816-
seen[hint.String()] = struct{}{}
817-
hints = append(hints, hint)
843+
seen[hint.index] = struct{}{}
844+
options = append(options, hint)
818845
}
846+
hintOptions = append(hintOptions, options)
819847
}
820-
return hints
848+
return hintOptions
821849
}

pkg/bindinfo/binding_plan_generation_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ func TestStartState(t *testing.T) {
109109
}
110110
fixes := []uint64{fixcontrol.Fix44855, fixcontrol.Fix45132, fixcontrol.Fix52869}
111111

112-
state, err := getStartState(vars, fixes)
112+
state, err := getStartState(vars, fixes, 0)
113113
require.NoError(t, err)
114114
require.Equal(t, state.Encode(), "1.0000,1.0000,1.0000,1.0000,1.0000,1.0000,1.0000,1.0000,1.0000,1.0000,1.0000,1.0000,1.0000,1.0000,1.0000,1.0000,1.0000,0.0100,0.0000,0.0000,0.0000,0.8000,true,false,false,0.0000,OFF,1000,OFF")
115115
}

pkg/bindinfo/testdata/binding_auto_suite_out.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,15 @@
9494
" └─Selection 9990.00 cop[tikv] not(isnull(test.t2.a))",
9595
" └─TableFullScan 10000.00 cop[tikv] table:t2 keep order:false, stats:pseudo"
9696
],
97+
[
98+
"HashJoin 12487.50 root inner join, equal:[eq(test.t1.a, test.t2.a)]",
99+
"├─IndexLookUp(Probe) 9990.00 root ",
100+
"│ ├─IndexFullScan(Build) 9990.00 cop[tikv] table:t1, index:a(a) keep order:false, stats:pseudo",
101+
"│ └─TableRowIDScan(Probe) 9990.00 cop[tikv] table:t1 keep order:false, stats:pseudo",
102+
"└─IndexLookUp(Build) 9990.00 root ",
103+
" ├─IndexFullScan(Build) 9990.00 cop[tikv] table:t2, index:a(a) keep order:false, stats:pseudo",
104+
" └─TableRowIDScan(Probe) 9990.00 cop[tikv] table:t2 keep order:false, stats:pseudo"
105+
],
97106
[
98107
"HashJoin 12487.50 root inner join, equal:[eq(test.t1.a, test.t2.a)]",
99108
"├─TableReader(Probe) 9990.00 root data:Selection",

0 commit comments

Comments
 (0)