From 64be9ec4c0c59b7f9827b1b5b0318ade177afb9a Mon Sep 17 00:00:00 2001 From: Yuanjia Zhang Date: Wed, 26 Jul 2023 21:57:04 +0800 Subject: [PATCH] planner: refine `tryToGetIndexJoin` (#45587) ref pingcap/tidb#45520 --- planner/core/exhaust_physical_plans.go | 126 ++++++++++++------------- planner/core/planbuilder.go | 3 + 2 files changed, 66 insertions(+), 63 deletions(-) diff --git a/planner/core/exhaust_physical_plans.go b/planner/core/exhaust_physical_plans.go index 748e2d9563d76..77cc854995465 100644 --- a/planner/core/exhaust_physical_plans.go +++ b/planner/core/exhaust_physical_plans.go @@ -1969,61 +1969,82 @@ func filterIndexJoinBySessionVars(sc sessionctx.Context, indexJoins []PhysicalPl return indexJoins } -// 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. -func (p *LogicalJoin) tryToGetIndexJoin(prop *property.PhysicalProperty) (indexJoins []PhysicalPlan, canForced bool) { - inljRightOuter := (p.preferJoinType & preferLeftAsINLJInner) > 0 - inljLeftOuter := (p.preferJoinType & preferRightAsINLJInner) > 0 - hasINLJHint := inljLeftOuter || inljRightOuter +func (p *LogicalJoin) preferAny(joinFlags ...uint) bool { + for _, flag := range joinFlags { + if p.preferJoinType&flag > 0 { + return true + } + } + return false +} - inlhjRightOuter := (p.preferJoinType & preferLeftAsINLHJInner) > 0 - inlhjLeftOuter := (p.preferJoinType & preferRightAsINLHJInner) > 0 - hasINLHJHint := inlhjLeftOuter || inlhjRightOuter +// satisfyIndexJoinHint returns whether this join plan can satisfy current index join hints. +func (p *LogicalJoin) satisfyIndexJoinHint(join PhysicalPlan) bool { + const left, right = 0, 1 + const indexJoin, indexHashJoin, indexMergeJoin = 0, 1, 2 + var innerSide, innerIdx, joinMethod int + switch ij := join.(type) { + case *PhysicalIndexJoin: + innerIdx = ij.getInnerChildIdx() + joinMethod = indexJoin + case *PhysicalIndexHashJoin: + innerIdx = ij.getInnerChildIdx() + joinMethod = indexHashJoin + case *PhysicalIndexMergeJoin: + innerIdx = ij.getInnerChildIdx() + joinMethod = indexMergeJoin + default: + return false + } + innerSide = left + if innerIdx == 1 { + innerSide = right + } - inlmjRightOuter := (p.preferJoinType & preferLeftAsINLMJInner) > 0 - inlmjLeftOuter := (p.preferJoinType & preferRightAsINLMJInner) > 0 - hasINLMJHint := inlmjLeftOuter || inlmjRightOuter + 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 +} - forceLeftOuter := inljLeftOuter || inlhjLeftOuter || inlmjLeftOuter - forceRightOuter := inljRightOuter || inlhjRightOuter || inlmjRightOuter +// 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. +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() { - // refine error message + // 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 hasINLJHint: - errMsg = "Optimizer Hint INL_JOIN or TIDB_INLJ is inapplicable" - case hasINLHJHint: - errMsg = "Optimizer Hint INL_HASH_JOIN is inapplicable" - case hasINLMJHint: - errMsg = "Optimizer Hint INL_MERGE_JOIN is inapplicable" - } - if p.hintInfo != nil && p.preferJoinType > 0 { - t := p.hintInfo.indexNestedLoopJoinTables - switch { - case len(t.inljTables) != 0 && ((p.preferJoinType&preferLeftAsINLJInner > 0) || (p.preferJoinType&preferRightAsINLJInner > 0)): - errMsg = fmt.Sprintf("Optimizer Hint %s or %s is inapplicable", - restore2JoinHint(HintINLJ, t.inljTables), restore2JoinHint(TiDBIndexNestedLoopJoin, t.inljTables)) - case len(t.inlhjTables) != 0 && ((p.preferJoinType&preferLeftAsINLHJInner > 0) || (p.preferJoinType&preferRightAsINLHJInner > 0)): - errMsg = fmt.Sprintf("Optimizer Hint %s is inapplicable", restore2JoinHint(HintINLHJ, t.inlhjTables)) - case len(t.inlmjTables) != 0 && ((p.preferJoinType&preferLeftAsINLMJInner > 0) || (p.preferJoinType&preferRightAsINLMJInner > 0)): - errMsg = fmt.Sprintf("Optimizer Hint %s is inapplicable", restore2JoinHint(HintINLMJ, t.inlmjTables)) - } + 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. - warning := ErrInternal.GenWithStack(errMsg) - p.SCtx().GetSessionVars().StmtCtx.AppendWarning(warning) + p.SCtx().GetSessionVars().StmtCtx.AppendWarning(ErrInternal.GenWithStack(errMsg)) } }() @@ -2044,19 +2065,8 @@ func (p *LogicalJoin) tryToGetIndexJoin(prop *property.PhysicalProperty) (indexJ allLeftOuterJoins = p.getIndexJoinByOuterIdx(prop, 0) forcedLeftOuterJoins = make([]PhysicalPlan, 0, len(allLeftOuterJoins)) for _, j := range allLeftOuterJoins { - switch j.(type) { - case *PhysicalIndexJoin: - if inljLeftOuter { - forcedLeftOuterJoins = append(forcedLeftOuterJoins, j) - } - case *PhysicalIndexHashJoin: - if inlhjLeftOuter { - forcedLeftOuterJoins = append(forcedLeftOuterJoins, j) - } - case *PhysicalIndexMergeJoin: - if inlmjLeftOuter { - forcedLeftOuterJoins = append(forcedLeftOuterJoins, j) - } + if p.satisfyIndexJoinHint(j) { + forcedLeftOuterJoins = append(forcedLeftOuterJoins, j) } } switch { @@ -2066,23 +2076,13 @@ func (p *LogicalJoin) tryToGetIndexJoin(prop *property.PhysicalProperty) (indexJ return forcedLeftOuterJoins, true } } + if supportRightOuter { allRightOuterJoins = p.getIndexJoinByOuterIdx(prop, 1) forcedRightOuterJoins = make([]PhysicalPlan, 0, len(allRightOuterJoins)) for _, j := range allRightOuterJoins { - switch j.(type) { - case *PhysicalIndexJoin: - if inljRightOuter { - forcedRightOuterJoins = append(forcedRightOuterJoins, j) - } - case *PhysicalIndexHashJoin: - if inlhjRightOuter { - forcedRightOuterJoins = append(forcedRightOuterJoins, j) - } - case *PhysicalIndexMergeJoin: - if inlmjRightOuter { - forcedRightOuterJoins = append(forcedRightOuterJoins, j) - } + if p.satisfyIndexJoinHint(j) { + forcedRightOuterJoins = append(forcedRightOuterJoins, j) } } switch { diff --git a/planner/core/planbuilder.go b/planner/core/planbuilder.go index 4d7ef409d254a..d0931463e01f8 100644 --- a/planner/core/planbuilder.go +++ b/planner/core/planbuilder.go @@ -343,6 +343,9 @@ func restore2TableHint(hintTables ...hintTableInfo) string { } func restore2JoinHint(hintType string, hintTables []hintTableInfo) string { + if len(hintTables) == 0 { + return strings.ToUpper(hintType) + } buffer := bytes.NewBufferString("/*+ ") buffer.WriteString(strings.ToUpper(hintType)) buffer.WriteString("(")