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: refactor tryToGetIndexJoin #45617

Merged
merged 8 commits into from
Jul 28, 2023
Merged
Changes from 6 commits
Commits
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
150 changes: 69 additions & 81 deletions planner/core/exhaust_physical_plans.go
Original file line number Diff line number Diff line change
Expand Up @@ -1978,11 +1978,10 @@ func (p *LogicalJoin) preferAny(joinFlags ...uint) bool {
return false
}

// satisfyIndexJoinHint returns whether this join plan can satisfy current index join hints.
func (p *LogicalJoin) satisfyIndexJoinHint(join PhysicalPlan) bool {
func (*LogicalJoin) getIndexJoinSideAndMethod(join PhysicalPlan) (innerSide, joinMethod int, ok bool) {
const left, right = 0, 1
const indexJoin, indexHashJoin, indexMergeJoin = 0, 1, 2
var innerSide, innerIdx, joinMethod int
var innerIdx int
switch ij := join.(type) {
case *PhysicalIndexJoin:
innerIdx = ij.getInnerChildIdx()
Expand All @@ -1994,60 +1993,18 @@ func (p *LogicalJoin) satisfyIndexJoinHint(join PhysicalPlan) bool {
innerIdx = ij.getInnerChildIdx()
joinMethod = indexMergeJoin
default:
return false
hawkingrei marked this conversation as resolved.
Show resolved Hide resolved
return 0, 0, false
}
ok = true
innerSide = left
if innerIdx == 1 {
innerSide = right
}

if (p.preferAny(preferLeftAsINLJInner) && innerSide == left && joinMethod == indexJoin) ||
(p.preferAny(preferRightAsINLJInner) && innerSide == right && joinMethod == indexJoin) ||
(p.preferAny(preferLeftAsINLHJInner) && innerSide == left && joinMethod == indexHashJoin) ||
(p.preferAny(preferRightAsINLHJInner) && innerSide == right && joinMethod == indexHashJoin) ||
(p.preferAny(preferLeftAsINLMJInner) && innerSide == left && joinMethod == indexMergeJoin) ||
(p.preferAny(preferRightAsINLMJInner) && innerSide == right && joinMethod == indexMergeJoin) {
return true
}
return false
return
}

// tryToGetIndexJoin will get index join by hints. If we can generate a valid index join by hint, the second return value
// will be true, which means we force to choose this index join. Otherwise we will select a join algorithm with min-cost.
// tryToGetIndexJoin returns all available index join plans, and the second returned value indicates whether this plan is enforced by hints.
func (p *LogicalJoin) tryToGetIndexJoin(prop *property.PhysicalProperty) (indexJoins []PhysicalPlan, canForced bool) {
forceLeftOuter := p.preferAny(preferRightAsINLJInner, preferRightAsINLHJInner, preferRightAsINLMJInner) // left as outer == right as inner
forceRightOuter := p.preferAny(preferLeftAsINLJInner, preferLeftAsINLHJInner, preferLeftAsINLMJInner) // right as outer == left as inner
needForced := forceLeftOuter || forceRightOuter

defer func() {
// Print warning message if any hints cannot work.
// If the required property is not empty, we will enforce it and try the hint again.
// So we only need to generate warning message when the property is empty.
if !canForced && needForced && prop.IsSortItemEmpty() {
// Construct warning message prefix.
var indexJoinTables, indexHashJoinTables, indexMergeJoinTables []hintTableInfo
if p.hintInfo != nil {
t := p.hintInfo.indexNestedLoopJoinTables
indexJoinTables, indexHashJoinTables, indexMergeJoinTables = t.inljTables, t.inlhjTables, t.inlmjTables
}
var errMsg string
switch {
case p.preferAny(preferLeftAsINLJInner, preferRightAsINLJInner): // prefer index join
errMsg = fmt.Sprintf("Optimizer Hint %s or %s is inapplicable", restore2JoinHint(HintINLJ, indexJoinTables), restore2JoinHint(TiDBIndexNestedLoopJoin, indexJoinTables))
case p.preferAny(preferLeftAsINLHJInner, preferRightAsINLHJInner): // prefer index hash join
errMsg = fmt.Sprintf("Optimizer Hint %s is inapplicable", restore2JoinHint(HintINLHJ, indexHashJoinTables))
case p.preferAny(preferLeftAsINLMJInner, preferRightAsINLMJInner): // prefer index merge join
errMsg = fmt.Sprintf("Optimizer Hint %s is inapplicable", restore2JoinHint(HintINLMJ, indexMergeJoinTables))
}
// Append inapplicable reason.
if len(p.EqualConditions) == 0 {
errMsg += " without column equal ON condition"
}
// Generate warning message to client.
p.SCtx().GetSessionVars().StmtCtx.AppendWarning(ErrInternal.GenWithStack(errMsg))
}
}()

// supportLeftOuter and supportRightOuter indicates whether this type of join
// supports the left side or right side to be the outer side.
var supportLeftOuter, supportRightOuter bool
Expand All @@ -2059,47 +2016,78 @@ func (p *LogicalJoin) tryToGetIndexJoin(prop *property.PhysicalProperty) (indexJ
case InnerJoin:
supportLeftOuter, supportRightOuter = true, true
}

var allLeftOuterJoins, allRightOuterJoins, forcedLeftOuterJoins, forcedRightOuterJoins []PhysicalPlan
candidates := make([]PhysicalPlan, 0, 2)
if supportLeftOuter {
allLeftOuterJoins = p.getIndexJoinByOuterIdx(prop, 0)
forcedLeftOuterJoins = make([]PhysicalPlan, 0, len(allLeftOuterJoins))
for _, j := range allLeftOuterJoins {
if p.satisfyIndexJoinHint(j) {
forcedLeftOuterJoins = append(forcedLeftOuterJoins, j)
}
candidates = append(candidates, p.getIndexJoinByOuterIdx(prop, 0)...)
}
if supportRightOuter {
candidates = append(candidates, p.getIndexJoinByOuterIdx(prop, 1)...)
}

// handle hints and variables about index join.
// the priority is: force hints like TIDB_INLJ > filter hints like NO_INDEX_JOIN > variables.
candidates, canForced = p.handleForceIndexJoinHints(prop, candidates)
if canForced {
return candidates, canForced
}
return filterIndexJoinBySessionVars(p.SCtx(), candidates), false
}

// handleForceIndexJoinHints handles the force index join hints and returns all plans that can satisfy the hints.
func (p *LogicalJoin) handleForceIndexJoinHints(prop *property.PhysicalProperty, candidates []PhysicalPlan) (indexJoins []PhysicalPlan, canForced bool) {
if !p.preferAny(preferRightAsINLJInner, preferRightAsINLHJInner, preferRightAsINLMJInner,
preferLeftAsINLJInner, preferLeftAsINLHJInner, preferLeftAsINLMJInner) {
return candidates, false // no force index join hints
}

const left, right = 0, 1
const indexJoin, indexHashJoin, indexMergeJoin = 0, 1, 2
Copy link
Member

Choose a reason for hiding this comment

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

We defined the same thing in getIndexJoinSideAndMethod. It's better to combine them into one definition.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

updated, PTAL

forced := make([]PhysicalPlan, 0, len(candidates))
for _, candidate := range candidates {
innerSide, joinMethod, ok := p.getIndexJoinSideAndMethod(candidate)
if !ok {
continue
}
switch {
case len(forcedLeftOuterJoins) == 0 && !supportRightOuter:
return filterIndexJoinBySessionVars(p.SCtx(), allLeftOuterJoins), false
case len(forcedLeftOuterJoins) != 0 && (!supportRightOuter || (forceLeftOuter && !forceRightOuter)):
return forcedLeftOuterJoins, true
if (p.preferAny(preferLeftAsINLJInner) && innerSide == left && joinMethod == indexJoin) ||
(p.preferAny(preferRightAsINLJInner) && innerSide == right && joinMethod == indexJoin) ||
(p.preferAny(preferLeftAsINLHJInner) && innerSide == left && joinMethod == indexHashJoin) ||
(p.preferAny(preferRightAsINLHJInner) && innerSide == right && joinMethod == indexHashJoin) ||
(p.preferAny(preferLeftAsINLMJInner) && innerSide == left && joinMethod == indexMergeJoin) ||
(p.preferAny(preferRightAsINLMJInner) && innerSide == right && joinMethod == indexMergeJoin) {
forced = append(forced, candidate)
}
}

if supportRightOuter {
allRightOuterJoins = p.getIndexJoinByOuterIdx(prop, 1)
forcedRightOuterJoins = make([]PhysicalPlan, 0, len(allRightOuterJoins))
for _, j := range allRightOuterJoins {
if p.satisfyIndexJoinHint(j) {
forcedRightOuterJoins = append(forcedRightOuterJoins, j)
}
if len(forced) > 0 {
return forced, true
}
// Cannot find any valid index join plan with these force hints.
// Print warning message if any hints cannot work.
// If the required property is not empty, we will enforce it and try the hint again.
// So we only need to generate warning message when the property is empty.
if prop.IsSortItemEmpty() {
var indexJoinTables, indexHashJoinTables, indexMergeJoinTables []hintTableInfo
if p.hintInfo != nil {
t := p.hintInfo.indexNestedLoopJoinTables
indexJoinTables, indexHashJoinTables, indexMergeJoinTables = t.inljTables, t.inlhjTables, t.inlmjTables
}
var errMsg string
switch {
case len(forcedRightOuterJoins) == 0 && !supportLeftOuter:
return filterIndexJoinBySessionVars(p.SCtx(), allRightOuterJoins), false
case len(forcedRightOuterJoins) != 0 && (!supportLeftOuter || (forceRightOuter && !forceLeftOuter)):
return forcedRightOuterJoins, true
case p.preferAny(preferLeftAsINLJInner, preferRightAsINLJInner): // prefer index join
errMsg = fmt.Sprintf("Optimizer Hint %s or %s is inapplicable", restore2JoinHint(HintINLJ, indexJoinTables), restore2JoinHint(TiDBIndexNestedLoopJoin, indexJoinTables))
case p.preferAny(preferLeftAsINLHJInner, preferRightAsINLHJInner): // prefer index hash join
errMsg = fmt.Sprintf("Optimizer Hint %s is inapplicable", restore2JoinHint(HintINLHJ, indexHashJoinTables))
case p.preferAny(preferLeftAsINLMJInner, preferRightAsINLMJInner): // prefer index merge join
errMsg = fmt.Sprintf("Optimizer Hint %s is inapplicable", restore2JoinHint(HintINLMJ, indexMergeJoinTables))
}
// Append inapplicable reason.
if len(p.EqualConditions) == 0 {
errMsg += " without column equal ON condition"
}
// Generate warning message to client.
p.SCtx().GetSessionVars().StmtCtx.AppendWarning(ErrInternal.GenWithStack(errMsg))
}

canForceLeft := len(forcedLeftOuterJoins) != 0 && forceLeftOuter
canForceRight := len(forcedRightOuterJoins) != 0 && forceRightOuter
canForced = canForceLeft || canForceRight
if canForced {
return append(forcedLeftOuterJoins, forcedRightOuterJoins...), true
}
return filterIndexJoinBySessionVars(p.SCtx(), append(allLeftOuterJoins, allRightOuterJoins...)), false
return candidates, false
}

func checkChildFitBC(p Plan) bool {
Expand Down