@@ -134,12 +134,12 @@ func (gp *genedPlan) PlanText() string {
134134
135135// state represents a state of the optimizer variables and fixes.
136136type 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
183186func 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
206213func 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
225232func 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
270277func 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}
0 commit comments