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: support leading hint in join reorder optimization #34570

Merged
merged 29 commits into from
May 17, 2022
Merged
Show file tree
Hide file tree
Changes from 26 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
83a054c
planner: refactor the join reorder codes
Reminiscent May 5, 2022
b872695
fix ut
Reminiscent May 5, 2022
427dc16
Merge branch 'master' of github.com:pingcap/tidb into JoinOrderRefactor
Reminiscent May 5, 2022
3d7ef8b
Merge branch 'master' of github.com:pingcap/tidb into JoinOrderRefactor
Reminiscent May 11, 2022
5bf862b
resolve conflicts
Reminiscent May 11, 2022
a46ada2
Merge branch 'master' of github.com:pingcap/tidb into JoinOrderRefactor
Reminiscent May 11, 2022
e7ded10
add more comments
Reminiscent May 11, 2022
8631387
planner: support leading hints in join reorder optimization
Reminiscent May 11, 2022
cdb8639
fix ut
Reminiscent May 11, 2022
5402c20
Merge branch 'master' of github.com:pingcap/tidb into planner-support…
Reminiscent May 11, 2022
f6f1eca
Merge branch 'master' of github.com:pingcap/tidb into planner-support…
Reminiscent May 12, 2022
88a2bb1
fix check_dev
Reminiscent May 12, 2022
919628c
add more test cases
Reminiscent May 12, 2022
867e379
Merge branch 'master' of github.com:pingcap/tidb into planner-support…
Reminiscent May 12, 2022
3226deb
fix ut
Reminiscent May 12, 2022
098ef46
fix ut
Reminiscent May 12, 2022
1bd70df
add the warning messages for the test cases
Reminiscent May 13, 2022
2ec1a7c
fix ut and address comments
Reminiscent May 13, 2022
150ad6f
Merge branch 'master' of github.com:pingcap/tidb into planner-support…
Reminiscent May 13, 2022
bae8681
fix test
Reminiscent May 13, 2022
b3e0ed9
Merge branch 'master' of github.com:pingcap/tidb into planner-support…
Reminiscent May 13, 2022
8bf2548
fix ut
Reminiscent May 13, 2022
1fa1c8c
forbid outer join
Reminiscent May 16, 2022
cf3b5a4
Merge branch 'master' of github.com:pingcap/tidb into planner-support…
Reminiscent May 16, 2022
2ad7cf9
address comments
Reminiscent May 16, 2022
34ad152
Merge branch 'master' of github.com:pingcap/tidb into planner-support…
Reminiscent May 16, 2022
6c03a66
address comments
Reminiscent May 16, 2022
3ddd5c9
Merge branch 'master' of github.com:pingcap/tidb into planner-support…
Reminiscent May 16, 2022
19f8993
Merge branch 'master' into planner-support-leading-hint
qw4990 May 17, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion planner/core/exhaust_physical_plans.go
Original file line number Diff line number Diff line change
Expand Up @@ -1657,7 +1657,7 @@ func (p *LogicalJoin) tryToGetIndexJoin(prop *property.PhysicalProperty) (indexJ
case hasINLMJHint:
errMsg = "Optimizer Hint INL_MERGE_JOIN is inapplicable"
}
if p.hintInfo != nil {
if p.hintInfo != nil && p.preferJoinType > 0 {
qw4990 marked this conversation as resolved.
Show resolved Hide resolved
t := p.hintInfo.indexNestedLoopJoinTables
switch {
case len(t.inljTables) != 0:
Expand Down
2 changes: 1 addition & 1 deletion planner/core/expression_rewriter.go
Original file line number Diff line number Diff line change
Expand Up @@ -953,7 +953,7 @@ func (er *expressionRewriter) handleInSubquery(ctx context.Context, v *ast.Patte
join.AttachOnConds(expression.SplitCNFItems(checkCondition))
// Set join hint for this join.
if er.b.TableHints() != nil {
join.setPreferredJoinType(er.b.TableHints())
join.setPreferredJoinTypeAndOrder(er.b.TableHints())
}
er.p = join
} else {
Expand Down
30 changes: 26 additions & 4 deletions planner/core/logical_plan_builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@ const (

// HintStraightJoin causes TiDB to join tables in the order in which they appear in the FROM clause.
HintStraightJoin = "straight_join"
// HintLeading specifies the set of tables to be used as the prefix in the execution plan.
HintLeading = "leading"

// TiDBIndexNestedLoopJoin is hint enforce index nested loop join.
TiDBIndexNestedLoopJoin = "tidb_inlj"
Expand Down Expand Up @@ -554,7 +556,7 @@ func extractTableAlias(p Plan, parentOffset int) *hintTableInfo {
return nil
}

func (p *LogicalJoin) setPreferredJoinType(hintInfo *tableHintInfo) {
func (p *LogicalJoin) setPreferredJoinTypeAndOrder(hintInfo *tableHintInfo) {
if hintInfo == nil {
return
}
Expand Down Expand Up @@ -594,8 +596,12 @@ func (p *LogicalJoin) setPreferredJoinType(hintInfo *tableHintInfo) {
p.ctx.GetSessionVars().StmtCtx.AppendWarning(warning)
p.preferJoinType = 0
}
// set the join order
if hintInfo.leadingJoinOrder != nil {
p.preferJoinOrder = hintInfo.matchTableName([]*hintTableInfo{lhsAlias, rhsAlias}, hintInfo.leadingJoinOrder)
}
// set hintInfo for further usage if this hint info can be used.
if p.preferJoinType != 0 {
if p.preferJoinType != 0 || p.preferJoinOrder {
p.hintInfo = hintInfo
}
}
Expand Down Expand Up @@ -767,7 +773,7 @@ func (b *PlanBuilder) buildJoin(ctx context.Context, joinNode *ast.Join) (Logica
}

// Set preferred join algorithm if some join hints is specified by user.
joinPlan.setPreferredJoinType(b.TableHints())
joinPlan.setPreferredJoinTypeAndOrder(b.TableHints())

// "NATURAL JOIN" doesn't have "ON" or "USING" conditions.
//
Expand Down Expand Up @@ -3511,12 +3517,14 @@ func (b *PlanBuilder) pushTableHints(hints []*ast.TableOptimizerHint, currentLev
aggHints aggHintInfo
timeRangeHint ast.HintTimeRange
limitHints limitHintInfo
leadingJoinOrder []hintTableInfo
leadingHintCnt int
)
for _, hint := range hints {
// Set warning for the hint that requires the table name.
switch hint.HintName.L {
case TiDBMergeJoin, HintSMJ, TiDBIndexNestedLoopJoin, HintINLJ, HintINLHJ, HintINLMJ,
TiDBHashJoin, HintHJ, HintUseIndex, HintIgnoreIndex, HintForceIndex, HintIndexMerge:
TiDBHashJoin, HintHJ, HintUseIndex, HintIgnoreIndex, HintForceIndex, HintIndexMerge, HintLeading:
if len(hint.Tables) == 0 {
b.pushHintWithoutTableWarning(hint)
continue
Expand Down Expand Up @@ -3613,10 +3621,22 @@ func (b *PlanBuilder) pushTableHints(hints []*ast.TableOptimizerHint, currentLev
timeRangeHint = hint.HintData.(ast.HintTimeRange)
case HintLimitToCop:
limitHints.preferLimitToCop = true
case HintLeading:
if leadingHintCnt == 0 {
leadingJoinOrder = append(leadingJoinOrder, tableNames2HintTableInfo(b.ctx, hint.HintName.L, hint.Tables, b.hintProcessor, currentLevel)...)
}
leadingHintCnt++
default:
// ignore hints that not implemented
}
}
if leadingHintCnt > 1 {
// If there are more leading hints, all leading hints will be invalid.
leadingJoinOrder = leadingJoinOrder[:0]
// Append warning if there are invalid index names.
errMsg := "We can only use one leading hint at most, when multiple leading hints are used, all leading hints will be invalid"
b.ctx.GetSessionVars().StmtCtx.AppendWarning(ErrInternal.GenWithStack(errMsg))
qw4990 marked this conversation as resolved.
Show resolved Hide resolved
}
b.tableHintInfo = append(b.tableHintInfo, tableHintInfo{
sortMergeJoinTables: sortMergeTables,
broadcastJoinTables: BCTables,
Expand All @@ -3629,6 +3649,7 @@ func (b *PlanBuilder) pushTableHints(hints []*ast.TableOptimizerHint, currentLev
indexMergeHintList: indexMergeHintList,
timeRangeHint: timeRangeHint,
limitHints: limitHints,
leadingJoinOrder: leadingJoinOrder,
})
}

Expand All @@ -3649,6 +3670,7 @@ func (b *PlanBuilder) popTableHints() {
b.appendUnmatchedJoinHintWarning(HintSMJ, TiDBMergeJoin, hintInfo.sortMergeJoinTables)
b.appendUnmatchedJoinHintWarning(HintBCJ, TiDBBroadCastJoin, hintInfo.broadcastJoinTables)
b.appendUnmatchedJoinHintWarning(HintHJ, TiDBHashJoin, hintInfo.hashJoinTables)
b.appendUnmatchedJoinHintWarning(HintLeading, "", hintInfo.leadingJoinOrder)
b.appendUnmatchedStorageHintWarning(hintInfo.tiflashTables, hintInfo.tikvTables)
b.tableHintInfo = b.tableHintInfo[:len(b.tableHintInfo)-1]
}
Expand Down
5 changes: 3 additions & 2 deletions planner/core/logical_plans.go
Original file line number Diff line number Diff line change
Expand Up @@ -136,8 +136,9 @@ type LogicalJoin struct {
StraightJoin bool

// hintInfo stores the join algorithm hint information specified by client.
hintInfo *tableHintInfo
preferJoinType uint
hintInfo *tableHintInfo
preferJoinType uint
preferJoinOrder bool

EqualConditions []*expression.ScalarFunction
LeftConditions expression.CNFExprs
Expand Down
3 changes: 2 additions & 1 deletion planner/core/planbuilder.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ type tableHintInfo struct {
indexMergeHintList []indexHintInfo
timeRangeHint ast.HintTimeRange
limitHints limitHintInfo
leadingJoinOrder []hintTableInfo
}

type limitHintInfo struct {
Expand Down Expand Up @@ -182,7 +183,7 @@ func tableNames2HintTableInfo(ctx sessionctx.Context, hintName string, hintTable
tableInfo.dbName = defaultDBName
}
switch hintName {
case TiDBMergeJoin, HintSMJ, TiDBIndexNestedLoopJoin, HintINLJ, HintINLHJ, HintINLMJ, TiDBHashJoin, HintHJ:
case TiDBMergeJoin, HintSMJ, TiDBIndexNestedLoopJoin, HintINLJ, HintINLHJ, HintINLMJ, TiDBHashJoin, HintHJ, HintLeading:
if len(tableInfo.partitions) > 0 {
isInapplicable = true
}
Expand Down
109 changes: 88 additions & 21 deletions planner/core/rule_join_reorder.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,15 +34,19 @@ import (
// For example: "InnerJoin(InnerJoin(a, b), LeftJoin(c, d))"
// results in a join group {a, b, c, d}.
func extractJoinGroup(p LogicalPlan) (group []LogicalPlan, eqEdges []*expression.ScalarFunction,
otherConds []expression.Expression, joinTypes []JoinType) {
otherConds []expression.Expression, joinTypes []JoinType, hintInfo *tableHintInfo, hasOuterJoin bool) {
join, isJoin := p.(*LogicalJoin)
if !isJoin || join.preferJoinType > uint(0) || join.StraightJoin ||
(join.JoinType != InnerJoin && join.JoinType != LeftOuterJoin && join.JoinType != RightOuterJoin) ||
((join.JoinType == LeftOuterJoin || join.JoinType == RightOuterJoin) && join.EqualConditions == nil) {
return []LogicalPlan{p}, nil, nil, nil
return []LogicalPlan{p}, nil, nil, nil, nil, false
}
if join.preferJoinOrder {
hintInfo = join.hintInfo
}
hasOuterJoin = hasOuterJoin || (join.JoinType != InnerJoin)
if join.JoinType != RightOuterJoin {
lhsGroup, lhsEqualConds, lhsOtherConds, lhsJoinTypes := extractJoinGroup(join.children[0])
lhsGroup, lhsEqualConds, lhsOtherConds, lhsJoinTypes, lhsHintInfo, lhsHasOuterJoin := extractJoinGroup(join.children[0])
noExpand := false
// If the filters of the outer join is related with multiple leaves of the outer join side. We don't reorder it for now.
if join.JoinType == LeftOuterJoin {
Expand All @@ -65,18 +69,22 @@ func extractJoinGroup(p LogicalPlan) (group []LogicalPlan, eqEdges []*expression
}
}
if noExpand {
return []LogicalPlan{p}, nil, nil, nil
return []LogicalPlan{p}, nil, nil, nil, nil, false
}
group = append(group, lhsGroup...)
eqEdges = append(eqEdges, lhsEqualConds...)
otherConds = append(otherConds, lhsOtherConds...)
joinTypes = append(joinTypes, lhsJoinTypes...)
if hintInfo == nil && lhsHintInfo != nil {
hintInfo = lhsHintInfo
}
hasOuterJoin = hasOuterJoin || lhsHasOuterJoin
} else {
group = append(group, join.children[0])
}

if join.JoinType != LeftOuterJoin {
rhsGroup, rhsEqualConds, rhsOtherConds, rhsJoinTypes := extractJoinGroup(join.children[1])
rhsGroup, rhsEqualConds, rhsOtherConds, rhsJoinTypes, rhsHintInfo, rhsHasOuterJoin := extractJoinGroup(join.children[1])
noExpand := false
// If the filters of the outer join is related with multiple leaves of the outer join side. We don't reorder it for now.
if join.JoinType == RightOuterJoin {
Expand All @@ -99,12 +107,16 @@ func extractJoinGroup(p LogicalPlan) (group []LogicalPlan, eqEdges []*expression
}
}
if noExpand {
return []LogicalPlan{p}, nil, nil, nil
return []LogicalPlan{p}, nil, nil, nil, nil, false
}
group = append(group, rhsGroup...)
eqEdges = append(eqEdges, rhsEqualConds...)
otherConds = append(otherConds, rhsOtherConds...)
joinTypes = append(joinTypes, rhsJoinTypes...)
if hintInfo == nil && rhsHintInfo != nil {
hintInfo = rhsHintInfo
}
hasOuterJoin = hasOuterJoin || rhsHasOuterJoin
} else {
group = append(group, join.children[1])
}
Expand All @@ -116,7 +128,7 @@ func extractJoinGroup(p LogicalPlan) (group []LogicalPlan, eqEdges []*expression
for range join.EqualConditions {
joinTypes = append(joinTypes, join.JoinType)
}
return group, eqEdges, otherConds, joinTypes
return group, eqEdges, otherConds, joinTypes, hintInfo, hasOuterJoin
}

type joinReOrderSolver struct {
Expand All @@ -140,21 +152,14 @@ func (s *joinReOrderSolver) optimize(ctx context.Context, p LogicalPlan, opt *lo
func (s *joinReOrderSolver) optimizeRecursive(ctx sessionctx.Context, p LogicalPlan, tracer *joinReorderTrace) (LogicalPlan, error) {
var err error

curJoinGroup, eqEdges, otherConds, joinTypes := extractJoinGroup(p)
curJoinGroup, eqEdges, otherConds, joinTypes, hintInfo, hasOuterJoin := extractJoinGroup(p)
if len(curJoinGroup) > 1 {
for i := range curJoinGroup {
curJoinGroup[i], err = s.optimizeRecursive(ctx, curJoinGroup[i], tracer)
if err != nil {
return nil, err
}
}
baseGroupSolver := &baseSingleGroupJoinOrderSolver{
ctx: ctx,
otherConds: otherConds,
eqEdges: eqEdges,
joinTypes: joinTypes,
}

originalSchema := p.Schema()

// Not support outer join reorder when using the DP algorithm
Expand All @@ -165,7 +170,35 @@ func (s *joinReOrderSolver) optimizeRecursive(ctx sessionctx.Context, p LogicalP
break
}
}
if len(curJoinGroup) > ctx.GetSessionVars().TiDBOptJoinReorderThreshold || !isSupportDP {

baseGroupSolver := &baseSingleGroupJoinOrderSolver{
ctx: ctx,
otherConds: otherConds,
eqEdges: eqEdges,
joinTypes: joinTypes,
}

joinGroupNum := len(curJoinGroup)
useGreedy := joinGroupNum > ctx.GetSessionVars().TiDBOptJoinReorderThreshold || !isSupportDP

if hintInfo != nil && hintInfo.leadingJoinOrder != nil {
if hasOuterJoin {
ctx.GetSessionVars().StmtCtx.AppendWarning(ErrInternal.GenWithStack("leading hint is inapplicable when we have outer join"))
qw4990 marked this conversation as resolved.
Show resolved Hide resolved
} else {
if useGreedy {
ok, leftJoinGroup := baseGroupSolver.generateLeadingJoinGroup(curJoinGroup, hintInfo)
if !ok {
ctx.GetSessionVars().StmtCtx.AppendWarning(ErrInternal.GenWithStack("leading hint is inapplicable, check if the leading hint table is valid"))
} else {
curJoinGroup = leftJoinGroup
}
} else {
ctx.GetSessionVars().StmtCtx.AppendWarning(ErrInternal.GenWithStack("leading hint is inapplicable for the DP join reorder algorithm"))
}
}
}

if useGreedy {
groupSolver := &joinReorderGreedySolver{
baseSingleGroupJoinOrderSolver: baseGroupSolver,
}
Expand Down Expand Up @@ -215,11 +248,45 @@ func (s *joinReOrderSolver) optimizeRecursive(ctx sessionctx.Context, p LogicalP

// nolint:structcheck
type baseSingleGroupJoinOrderSolver struct {
ctx sessionctx.Context
curJoinGroup []*jrNode
otherConds []expression.Expression
eqEdges []*expression.ScalarFunction
joinTypes []JoinType
ctx sessionctx.Context
curJoinGroup []*jrNode
otherConds []expression.Expression
eqEdges []*expression.ScalarFunction
joinTypes []JoinType
leadingJoinGroup LogicalPlan
}

func (s *baseSingleGroupJoinOrderSolver) generateLeadingJoinGroup(curJoinGroup []LogicalPlan, hintInfo *tableHintInfo) (bool, []LogicalPlan) {
var leadingJoinGroup []LogicalPlan
leftJoinGroup := make([]LogicalPlan, len(curJoinGroup))
copy(leftJoinGroup, curJoinGroup)
for _, hintTbl := range hintInfo.leadingJoinOrder {
for i, joinGroup := range leftJoinGroup {
tableAlias := extractTableAlias(joinGroup, joinGroup.SelectBlockOffset())
if tableAlias == nil {
continue
}
if hintTbl.dbName.L == tableAlias.dbName.L && hintTbl.tblName.L == tableAlias.tblName.L && hintTbl.selectOffset == tableAlias.selectOffset {
leadingJoinGroup = append(leadingJoinGroup, joinGroup)
leftJoinGroup = append(leftJoinGroup[:i], leftJoinGroup[i+1:]...)
break
}
}
}
if len(leadingJoinGroup) != len(hintInfo.leadingJoinOrder) || leadingJoinGroup == nil {
return false, nil
}
leadingJoin := leadingJoinGroup[0]
leadingJoinGroup = leadingJoinGroup[1:]
for len(leadingJoinGroup) > 0 {
var usedEdges []*expression.ScalarFunction
var joinType JoinType
leadingJoin, leadingJoinGroup[0], usedEdges, joinType = s.checkConnection(leadingJoin, leadingJoinGroup[0])
leadingJoin, s.otherConds = s.makeJoin(leadingJoin, leadingJoinGroup[0], usedEdges, joinType)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems weird that update an inside field with an attached method like _, s.otherConds = s.makeJoin(...)...
It's another problem and we can leave it unchanged here...

leadingJoinGroup = leadingJoinGroup[1:]
}
s.leadingJoinGroup = leadingJoin
return true, leftJoinGroup
}

// generateJoinOrderNode used to derive the stats for the joinNodePlans and generate the jrNode groups based on the cost.
Expand Down
15 changes: 15 additions & 0 deletions planner/core/rule_join_reorder_greedy.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,26 @@ func (s *joinReorderGreedySolver) solve(joinNodePlans []LogicalPlan, tracer *joi
if err != nil {
return nil, err
}
var leadingJoinNodes []*jrNode
if s.leadingJoinGroup != nil {
// We have a leading hint to let some tables join first. The result is stored in the s.leadingJoinGroup.
// We generate jrNode separately for it.
leadingJoinNodes, err = s.generateJoinOrderNode([]LogicalPlan{s.leadingJoinGroup}, tracer)
if err != nil {
return nil, err
}
}
// Sort plans by cost
sort.SliceStable(s.curJoinGroup, func(i, j int) bool {
return s.curJoinGroup[i].cumCost < s.curJoinGroup[j].cumCost
})

if leadingJoinNodes != nil {
// The leadingJoinNodes should be the first element in the s.curJoinGroup.
// So it can be joined first.
leadingJoinNodes := append(leadingJoinNodes, s.curJoinGroup...)
s.curJoinGroup = leadingJoinNodes
}
var cartesianGroup []LogicalPlan
for len(s.curJoinGroup) > 0 {
newNode, err := s.constructConnectedJoinTree(tracer)
Expand Down
Loading