planner: fix plan explore can not detect the specific index path.#65709
planner: fix plan explore can not detect the specific index path.#65709ti-chi-bot[bot] merged 4 commits intopingcap:masterfrom
Conversation
|
Hi @AilinKid. Thanks for your PR. PRs from untrusted users cannot be marked as trusted with I understand the commands that are listed here. DetailsInstructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository. |
There was a problem hiding this comment.
Pull request overview
This PR fixes an issue where EXPLAIN EXPLORE could miss alternative index paths for single-table queries when statistics favor one index over another. The fix allows the plan exploration to enumerate and test all candidate indexes that could be used based on the WHERE predicate columns.
Changes:
- Added index hint exploration to the breadth-first plan search algorithm
- Implemented column extraction from WHERE predicates to identify candidate indexes
- Extended the state representation to include index hints alongside existing leading hints and optimizer variables
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
| pkg/bindinfo/binding_plan_generation.go | Adds indexHint struct, updates state to track index hints, implements extractSelectIndexHints to identify candidate indexes from WHERE predicates, and integrates index hint exploration into the BFS plan search |
| pkg/bindinfo/binding_auto_test.go | Adds test case TestExplainExploreIndexHints to verify that EXPLAIN EXPLORE returns plans for both index a and index b |
| pkg/bindinfo/BUILD.bazel | Adds dependency on pkg/meta/model and increments test shard count from 48 to 49 |
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## master #65709 +/- ##
================================================
- Coverage 77.7795% 77.6318% -0.1478%
================================================
Files 2001 1923 -78
Lines 545624 533459 -12165
================================================
- Hits 424384 414134 -10250
+ Misses 119578 119319 -259
+ Partials 1662 6 -1656
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
| } | ||
| } | ||
| for _, indexHint := range possibleIndexHints { | ||
| newState := newStateWithIndexHint(currState, indexHint) |
There was a problem hiding this comment.
It seems like we only consider one index in a state, if there are multiple tables, should we consider multiple index hints?
There was a problem hiding this comment.
table levels, and for each table, there are multi index choices? make sense
There was a problem hiding this comment.
i will do it in next pr
|
|
||
| func extractSelectIndexHints(sctx sessionctx.Context, node ast.StmtNode, tableNames []*tableName) []*indexHint { | ||
| selStmt, isSel := node.(*ast.SelectStmt) | ||
| if !isSel || selStmt.Where == nil || len(tableNames) != 1 { |
There was a problem hiding this comment.
Why we have such restriction that only support one table?
Does it means explain explore will not support explore index for multiple table query?
There was a problem hiding this comment.
we will expand to all tableNames in next pr
There was a problem hiding this comment.
Guess we should have a sys var to control this behavior.
| tk.MustExec("use test") | ||
| tk.MustExec(`create table t (a int, b int, c int, key(a), key(b))`) | ||
|
|
||
| rows := tk.MustQuery(`explain explore select * from t where a=1 and b=1`).Rows() |
There was a problem hiding this comment.
suggestion: add case that has alias table name to cover this situation
There was a problem hiding this comment.
looks like we failed to explore index b when adding table alias:
TiDB root@127.0.0.1:test> explain explore select 1 from t t_alias where a=1 and b=1 and c like "%xx%"\G
***************************[ 1. row ]***************************
statement | select 1 from t t_alias where a=1 and b=1 and c like "%xx%"
binding_hint | use_index(@`sel_1` `test`.`t_alias` )
plan | Projection 1.00 root 1->Column#5
└─TableReader 1.00 root data:Selection
└─Selection 1.00 cop[tikv] eq(test.t.a, 1), eq(test.t.b, 1), like(cast(test.t.c, var_string(20)), "%xx%", 92)
└─TableFullScan 2000.00 cop[tikv] table:t_alias keep order:false, stats:partial[a:allEvicted, b:allEvicted, a:allEvicted...(more: 2 allEvicted)]
plan_digest | 2d880578187453ef4bec09bd049c955f5e558c549d4e6c0f937b557ec82e8ea8
avg_latency | 0
exec_times | 0
avg_scan_rows | 0
avg_returned_rows | 0
latency_per_returned_row | 0
scan_rows_per_returned_row | 0
recommend |
reason |
explain_analyze | EXPLAIN ANALYZE '2d880578187453ef4bec09bd049c955f5e558c549d4e6c0f937b557ec82e8ea8'
binding | CREATE GLOBAL BINDING FROM HISTORY USING PLAN DIGEST '2d880578187453ef4bec09bd049c955f5e558c549d4e6c0f937b557ec82e8ea8'
***************************[ 2. row ]***************************
statement | select 1 from t t_alias where a=1 and b=1 and c like "%xx%"
binding_hint | use_index(@`sel_1` `test`.`t_alias` `a`), no_order_index(@`sel_1` `test`.`t_alias` `a`)
plan | Projection 1.00 root 1->Column#5
└─IndexLookUp 1.00 root
├─IndexRangeScan(Build) 2.00 cop[tikv] table:t_alias, index:a(a) range:[1,1], keep order:false, stats:partial[a:allEvicted, b:allEvicted, a:allEvicted...(more: 2 allEvicted)]
└─Selection(Probe) 1.00 cop[tikv] eq(test.t.b, 1), like(cast(test.t.c, var_string(20)), "%xx%", 92)
└─TableRowIDScan 2.00 cop[tikv] table:t_alias keep order:false, stats:partial[a:allEvicted, b:allEvicted, a:allEvicted...(more: 2 allEvicted)]
plan_digest | 53a2f3b7e7fd761ec1e6729d7680306785fae4ccb945f1a6790185ad3c586dd0
avg_latency | 0
exec_times | 0
avg_scan_rows | 0
avg_returned_rows | 0
latency_per_returned_row | 0
scan_rows_per_returned_row | 0
recommend |
reason |
explain_analyze | EXPLAIN ANALYZE '53a2f3b7e7fd761ec1e6729d7680306785fae4ccb945f1a6790185ad3c586dd0'
binding | CREATE GLOBAL BINDING FROM HISTORY USING PLAN DIGEST '53a2f3b7e7fd761ec1e6729d7680306785fae4ccb945f1a6790185ad3c586dd0'
2 rows in set
Time: 0.482s
There was a problem hiding this comment.
make sense, addressed
|
|
||
| func (e *predicateColumnExtractor) Enter(in ast.Node) (node ast.Node, skipChildren bool) { | ||
| switch n := in.(type) { | ||
| case *ast.SubqueryExpr: |
There was a problem hiding this comment.
question: why skip SubqueryExpr?
There was a problem hiding this comment.
different select block will mix the column name
select a from t, (select a from t2) x. this case we will fetch column name a, but hard to tell this a is from t or t2, so it will cause misunderstanding here, i will do it in next pr.
| func extractSelectIndexHints(sctx sessionctx.Context, defaultSchema string, node ast.StmtNode) []*indexHint { | ||
| selStmt, isSel := node.(*ast.SelectStmt) | ||
| if !isSel { | ||
| return nil | ||
| } | ||
| tableNames := extractSelectTableNamesWithAlias(defaultSchema, selStmt) | ||
| if len(tableNames) == 0 { | ||
| return nil | ||
| } | ||
| allowAnyTable := len(tableNames) == 1 | ||
| hints := make([]*indexHint, 0) | ||
| seen := make(map[string]struct{}) | ||
| for _, target := range tableNames { | ||
| extractor := &predicateColumnExtractor{ | ||
| table: target, | ||
| columns: make(map[string]struct{}), | ||
| allowAnyTable: allowAnyTable, | ||
| } | ||
| if selStmt.Where != nil { | ||
| selStmt.Where.Accept(extractor) | ||
| } | ||
| if selStmt.From != nil && selStmt.From.TableRefs != nil { | ||
| collectJoinPredicates(selStmt.From.TableRefs, extractor) | ||
| } | ||
| if len(extractor.columns) == 0 { | ||
| continue | ||
| } | ||
| tblInfo, err := sctx.GetLatestInfoSchema().TableInfoByName(ast.NewCIStr(target.schema), ast.NewCIStr(target.name)) | ||
| if err != nil { | ||
| continue | ||
| } | ||
| useInvisible := sctx.GetSessionVars().OptimizerUseInvisibleIndexes | ||
| for _, index := range tblInfo.Indices { | ||
| if index.State != model.StatePublic { | ||
| continue | ||
| } | ||
| if !useInvisible && index.Invisible { | ||
| continue | ||
| } | ||
| if index.IsColumnarIndex() || index.InvertedInfo != nil { | ||
| continue | ||
| } | ||
| if tblInfo.IsCommonHandle && index.Primary { | ||
| continue | ||
| } | ||
| if len(index.Columns) == 0 { | ||
| continue | ||
| } | ||
| if _, ok := extractor.columns[index.Columns[0].Name.L]; !ok { | ||
| continue | ||
| } | ||
| hint := &indexHint{ | ||
| table: target, | ||
| index: index.Name.O, | ||
| } | ||
| if _, ok := seen[hint.String()]; ok { | ||
| continue | ||
| } | ||
| seen[hint.String()] = struct{}{} | ||
| hints = append(hints, hint) | ||
| } | ||
| } | ||
| return hints |
There was a problem hiding this comment.
extractSelectIndexHints currently generates index hints for any SELECT with table references in the FROM clause, including multi-table joins (it iterates over all tableNames regardless of length), while the PR description states that exploration should be limited to single-table SELECTs with predicates. Please either constrain this helper (for example by returning early when len(tableNames) != 1) or update the PR description to accurately describe that index hints are also applied to joined queries.
|
/retest-required |
|
@AilinKid: PRs from untrusted users cannot be marked as trusted with DetailsIn response to this:
Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository. |
|
/ok-to-test |
|
[APPROVALNOTIFIER] This PR is APPROVED This pull-request has been approved by: guo-shaoge, qw4990 The full list of commands accepted by this bot can be found here. The pull request process is described here DetailsNeeds approval from an approver in each of these files:
Approvers can indicate their approval by writing |
|
/retest |
[LGTM Timeline notifier]Timeline:
|
|
/retest-required |
1 similar comment
|
/retest-required |
|
/retest |
|
/retest-required |
|
/retest |
11 similar comments
|
/retest |
|
/retest |
|
/retest |
|
/retest |
|
/retest |
|
/retest |
|
/retest |
|
/retest |
|
/retest |
|
/retest |
|
/retest |
|
/retest-required |
83f102c to
ca41e03
Compare
|
/retest |
What problem does this PR solve?
Issue Number: close #65708
Problem Summary:
Explain Explore can miss alternative index paths for single-table queries when statistics favor one index (e.g., always picking
aand never exploringb), which makes ithard to surface candidate plans for plan binding or verification.
What changed and how does it work?
WHEREpredicate columns and explores them viaUSE_INDEXhints.SELECTwith predicates and skips subqueries to avoid unsafe hinting.EXPLAIN EXPLOREreturns bothindex:aandindex:bplans.Check List
Tests
Side effects
Documentation
Release note
Please refer to Release Notes Language Style Guide to write a quality release note.