Skip to content

Commit

Permalink
planner: support leading hint in join reorder optimization (#34570)
Browse files Browse the repository at this point in the history
ref #29932
  • Loading branch information
Reminiscent authored May 17, 2022
1 parent 683ba09 commit e255739
Show file tree
Hide file tree
Showing 10 changed files with 1,798 additions and 58 deletions.
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 {
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))
}
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"))
} 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)
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

0 comments on commit e255739

Please sign in to comment.