Skip to content

Commit

Permalink
planner: support hash_join_build and hash_join_probe hints (#36667)
Browse files Browse the repository at this point in the history
close #35439
  • Loading branch information
Reminiscent authored Sep 2, 2022
1 parent 6d27b3d commit 57c4938
Show file tree
Hide file tree
Showing 9 changed files with 467 additions and 211 deletions.
2 changes: 1 addition & 1 deletion cmd/explaintest/r/clustered_index.result
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ MergeJoin_7 2533.51 root right outer join, left key:with_cluster_index.tbl_2.co
└─TableFullScan_23 2244.00 cop[tikv] table:tbl_0 keep order:true
explain select tbl_2.col_14 , tbl_0.col_1 from wout_cluster_index.tbl_2 right join wout_cluster_index.tbl_0 on col_3 = col_11 ;
id estRows task access object operator info
HashJoin_23 2533.51 root right outer join, equal:[eq(wout_cluster_index.tbl_2.col_11, wout_cluster_index.tbl_0.col_3)]
HashJoin_22 2533.51 root right outer join, equal:[eq(wout_cluster_index.tbl_2.col_11, wout_cluster_index.tbl_0.col_3)]
├─TableReader_41(Build) 2244.00 root data:TableFullScan_40
│ └─TableFullScan_40 2244.00 cop[tikv] table:tbl_0 keep order:false
└─TableReader_44(Probe) 4509.00 root data:Selection_43
Expand Down
52 changes: 40 additions & 12 deletions planner/core/exhaust_physical_plans.go
Original file line number Diff line number Diff line change
Expand Up @@ -373,37 +373,65 @@ var ForceUseOuterBuild4Test = atomic.NewBool(false)
// TODO: use hint and remove this variable
var ForcedHashLeftJoin4Test = atomic.NewBool(false)

func (p *LogicalJoin) getHashJoins(prop *property.PhysicalProperty) []PhysicalPlan {
func (p *LogicalJoin) getHashJoins(prop *property.PhysicalProperty) (joins []PhysicalPlan, forced bool) {
if !prop.IsSortItemEmpty() { // hash join doesn't promise any orders
return nil
return
}
joins := make([]PhysicalPlan, 0, 2)
forceLeftToBuild := ((p.preferJoinType & preferLeftAsHJBuild) > 0) || ((p.preferJoinType & preferRightAsHJProbe) > 0)
forceRightToBuild := ((p.preferJoinType & preferRightAsHJBuild) > 0) || ((p.preferJoinType & preferLeftAsHJProbe) > 0)
if forceLeftToBuild && forceRightToBuild {
p.ctx.GetSessionVars().StmtCtx.AppendWarning(ErrInternal.GenWithStack("Some HASH_JOIN_BUILD and HASH_JOIN_PROBE hints are conflicts, please check the hints"))
forceLeftToBuild = false
forceRightToBuild = false
}
joins = make([]PhysicalPlan, 0, 2)
switch p.JoinType {
case SemiJoin, AntiSemiJoin, LeftOuterSemiJoin, AntiLeftOuterSemiJoin:
joins = append(joins, p.getHashJoin(prop, 1, false))
if forceLeftToBuild || forceRightToBuild {
// Do not support specifying the build side.
p.ctx.GetSessionVars().StmtCtx.AppendWarning(ErrInternal.GenWithStack(fmt.Sprintf("We can't use the HASH_JOIN_BUILD or HASH_JOIN_PROBE hint for %s, please check the hint", p.JoinType)))
forceLeftToBuild = false
forceRightToBuild = false
}
case LeftOuterJoin:
if ForceUseOuterBuild4Test.Load() {
joins = append(joins, p.getHashJoin(prop, 1, true))
} else {
joins = append(joins, p.getHashJoin(prop, 1, false))
joins = append(joins, p.getHashJoin(prop, 1, true))
if !forceLeftToBuild {
joins = append(joins, p.getHashJoin(prop, 1, false))
}
if !forceRightToBuild {
joins = append(joins, p.getHashJoin(prop, 1, true))
}
}
case RightOuterJoin:
if ForceUseOuterBuild4Test.Load() {
joins = append(joins, p.getHashJoin(prop, 0, true))
} else {
joins = append(joins, p.getHashJoin(prop, 0, false))
joins = append(joins, p.getHashJoin(prop, 0, true))
if !forceLeftToBuild {
joins = append(joins, p.getHashJoin(prop, 0, true))
}
if !forceRightToBuild {
joins = append(joins, p.getHashJoin(prop, 0, false))
}
}
case InnerJoin:
if ForcedHashLeftJoin4Test.Load() {
joins = append(joins, p.getHashJoin(prop, 1, false))
} else {
joins = append(joins, p.getHashJoin(prop, 1, false))
joins = append(joins, p.getHashJoin(prop, 0, false))
if forceLeftToBuild {
joins = append(joins, p.getHashJoin(prop, 0, false))
} else if forceRightToBuild {
joins = append(joins, p.getHashJoin(prop, 1, false))
} else {
joins = append(joins, p.getHashJoin(prop, 1, false))
joins = append(joins, p.getHashJoin(prop, 0, false))
}
}
}
return joins
forced = (p.preferJoinType&preferHashJoin > 0) || forceLeftToBuild || forceRightToBuild
return
}

func (p *LogicalJoin) getHashJoin(prop *property.PhysicalProperty, innerIdx int, useOuterToBuild bool) *PhysicalHashJoin {
Expand Down Expand Up @@ -1832,8 +1860,8 @@ func (p *LogicalJoin) exhaustPhysicalPlans(prop *property.PhysicalProperty) ([]P
}
joins = append(joins, indexJoins...)

hashJoins := p.getHashJoins(prop)
if (p.preferJoinType&preferHashJoin) > 0 && len(hashJoins) > 0 {
hashJoins, forced := p.getHashJoins(prop)
if forced && len(hashJoins) > 0 {
return hashJoins, true, nil
}
joins = append(joins, hashJoins...)
Expand Down
1 change: 1 addition & 0 deletions planner/core/hints.go
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,7 @@ func genHintsFromSingle(p PhysicalPlan, nodeType utilhint.NodeType, res []*ast.T
case *PhysicalMergeJoin:
res = append(res, getJoinHints(p.SCtx(), HintSMJ, p.SelectBlockOffset(), nodeType, pp.children...)...)
case *PhysicalHashJoin:
// TODO: support the hash_join_build and hash_join_probe hint for auto capture
res = append(res, getJoinHints(p.SCtx(), HintHJ, p.SelectBlockOffset(), nodeType, pp.children...)...)
case *PhysicalIndexJoin:
res = append(res, getJoinHints(p.SCtx(), HintINLJ, p.SelectBlockOffset(), nodeType, pp.children[pp.InnerChildIdx])...)
Expand Down
58 changes: 52 additions & 6 deletions planner/core/logical_plan_builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,10 @@ const (
TiDBHashJoin = "tidb_hj"
// HintHJ is hint enforce hash join.
HintHJ = "hash_join"
// HintHashJoinBuild is hint enforce hash join's build side
HintHashJoinBuild = "hash_join_build"
// HintHashJoinProbe is hint enforce hash join's probe side
HintHashJoinProbe = "hash_join_probe"
// HintHashAgg is hint enforce hash aggregation.
HintHashAgg = "hash_agg"
// HintStreamAgg is hint enforce stream aggregation.
Expand Down Expand Up @@ -599,6 +603,18 @@ func (p *LogicalJoin) setPreferredJoinTypeAndOrder(hintInfo *tableHintInfo) {
if hintInfo.ifPreferINLMJ(rhsAlias) {
p.preferJoinType |= preferRightAsINLMJInner
}
if hintInfo.ifPreferHJBuild(lhsAlias) {
p.preferJoinType |= preferLeftAsHJBuild
}
if hintInfo.ifPreferHJBuild(rhsAlias) {
p.preferJoinType |= preferRightAsHJBuild
}
if hintInfo.ifPreferHJProbe(lhsAlias) {
p.preferJoinType |= preferLeftAsHJProbe
}
if hintInfo.ifPreferHJProbe(rhsAlias) {
p.preferJoinType |= preferRightAsHJProbe
}
if containDifferentJoinTypes(p.preferJoinType) {
errMsg := "Join hints are conflict, you can only specify one type of join"
warning := ErrInternal.GenWithStack(errMsg)
Expand Down Expand Up @@ -3545,6 +3561,7 @@ func (b *PlanBuilder) pushTableHints(hints []*ast.TableOptimizerHint, currentLev
limitHints limitHintInfo
MergeHints MergeHintInfo
leadingJoinOrder []hintTableInfo
hjBuildTables, hjProbeTables []hintTableInfo
leadingHintCnt int
)
for _, hint := range hints {
Expand All @@ -3571,6 +3588,10 @@ func (b *PlanBuilder) pushTableHints(hints []*ast.TableOptimizerHint, currentLev
INLMJTables = append(INLMJTables, tableNames2HintTableInfo(b.ctx, hint.HintName.L, hint.Tables, b.hintProcessor, currentLevel)...)
case TiDBHashJoin, HintHJ:
hashJoinTables = append(hashJoinTables, tableNames2HintTableInfo(b.ctx, hint.HintName.L, hint.Tables, b.hintProcessor, currentLevel)...)
case HintHashJoinBuild:
hjBuildTables = append(hjBuildTables, tableNames2HintTableInfo(b.ctx, hint.HintName.L, hint.Tables, b.hintProcessor, currentLevel)...)
case HintHashJoinProbe:
hjProbeTables = append(hjProbeTables, tableNames2HintTableInfo(b.ctx, hint.HintName.L, hint.Tables, b.hintProcessor, currentLevel)...)
case HintHashAgg:
aggHints.preferAggType |= preferHashAgg
case HintStreamAgg:
Expand Down Expand Up @@ -3692,6 +3713,8 @@ func (b *PlanBuilder) pushTableHints(hints []*ast.TableOptimizerHint, currentLev
limitHints: limitHints,
MergeHints: MergeHints,
leadingJoinOrder: leadingJoinOrder,
hjBuildTables: hjBuildTables,
hjProbeTables: hjProbeTables,
})
}

Expand All @@ -3712,6 +3735,8 @@ func (b *PlanBuilder) popTableHints() {
b.appendUnmatchedJoinHintWarning(HintSMJ, TiDBMergeJoin, hintInfo.sortMergeJoinTables)
b.appendUnmatchedJoinHintWarning(HintBCJ, TiDBBroadCastJoin, hintInfo.broadcastJoinTables)
b.appendUnmatchedJoinHintWarning(HintHJ, TiDBHashJoin, hintInfo.hashJoinTables)
b.appendUnmatchedJoinHintWarning(HintHashJoinBuild, "", hintInfo.hjBuildTables)
b.appendUnmatchedJoinHintWarning(HintHashJoinProbe, "", hintInfo.hjProbeTables)
b.appendUnmatchedJoinHintWarning(HintLeading, "", hintInfo.leadingJoinOrder)
b.appendUnmatchedStorageHintWarning(hintInfo.tiflashTables, hintInfo.tikvTables)
b.tableHintInfo = b.tableHintInfo[:len(b.tableHintInfo)-1]
Expand Down Expand Up @@ -5058,23 +5083,36 @@ func (b *PlanBuilder) buildSemiJoin(outerPlan, innerPlan LogicalPlan, onConditio
}
// Apply forces to choose hash join currently, so don't worry the hints will take effect if the semi join is in one apply.
if b.TableHints() != nil {
hintInfo := b.TableHints()
outerAlias := extractTableAlias(outerPlan, joinPlan.blockOffset)
innerAlias := extractTableAlias(innerPlan, joinPlan.blockOffset)
if b.TableHints().ifPreferMergeJoin(outerAlias, innerAlias) {
if hintInfo.ifPreferMergeJoin(outerAlias, innerAlias) {
joinPlan.preferJoinType |= preferMergeJoin
}
if b.TableHints().ifPreferHashJoin(outerAlias, innerAlias) {
if hintInfo.ifPreferHashJoin(outerAlias, innerAlias) {
joinPlan.preferJoinType |= preferHashJoin
}
if b.TableHints().ifPreferINLJ(innerAlias) {
if hintInfo.ifPreferINLJ(innerAlias) {
joinPlan.preferJoinType = preferRightAsINLJInner
}
if b.TableHints().ifPreferINLHJ(innerAlias) {
if hintInfo.ifPreferINLHJ(innerAlias) {
joinPlan.preferJoinType = preferRightAsINLHJInner
}
if b.TableHints().ifPreferINLMJ(innerAlias) {
if hintInfo.ifPreferINLMJ(innerAlias) {
joinPlan.preferJoinType = preferRightAsINLMJInner
}
if hintInfo.ifPreferHJBuild(outerAlias) {
joinPlan.preferJoinType |= preferLeftAsHJBuild
}
if hintInfo.ifPreferHJBuild(innerAlias) {
joinPlan.preferJoinType |= preferRightAsHJBuild
}
if hintInfo.ifPreferHJProbe(outerAlias) {
joinPlan.preferJoinType |= preferLeftAsHJProbe
}
if hintInfo.ifPreferHJProbe(innerAlias) {
joinPlan.preferJoinType |= preferRightAsHJProbe
}
// If there're multiple join hints, they're conflict.
if bits.OnesCount(joinPlan.preferJoinType) > 1 {
return nil, errors.New("Join hints are conflict, you can only specify one type of join")
Expand Down Expand Up @@ -6706,8 +6744,10 @@ func containDifferentJoinTypes(preferJoinType uint) bool {
inlMask := preferRightAsINLJInner ^ preferLeftAsINLJInner
inlhjMask := preferRightAsINLHJInner ^ preferLeftAsINLHJInner
inlmjMask := preferRightAsINLMJInner ^ preferLeftAsINLMJInner
hjRightBuildMask := preferRightAsHJBuild ^ preferLeftAsHJProbe
hjLeftBuildMask := preferLeftAsHJBuild ^ preferRightAsHJProbe

mask := inlMask ^ inlhjMask ^ inlmjMask
mask := inlMask ^ inlhjMask ^ inlmjMask ^ hjRightBuildMask ^ hjLeftBuildMask
onesCount := bits.OnesCount(preferJoinType & ^mask)
if onesCount > 1 || onesCount == 1 && preferJoinType&mask > 0 {
return true
Expand All @@ -6723,6 +6763,12 @@ func containDifferentJoinTypes(preferJoinType uint) bool {
if preferJoinType&inlmjMask > 0 {
cnt++
}
if preferJoinType&hjLeftBuildMask > 0 {
cnt++
}
if preferJoinType&hjRightBuildMask > 0 {
cnt++
}
return cnt > 1
}

Expand Down
4 changes: 4 additions & 0 deletions planner/core/logical_plans.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,10 @@ const (
preferLeftAsINLMJInner
preferRightAsINLMJInner
preferHashJoin
preferLeftAsHJBuild
preferRightAsHJBuild
preferLeftAsHJProbe
preferRightAsHJProbe
preferMergeJoin
preferBCJoin
preferRewriteSemiJoin
Expand Down
33 changes: 33 additions & 0 deletions planner/core/physical_plan_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1994,6 +1994,39 @@ func TestSkewDistinctAgg(t *testing.T) {
}
}

func TestHJBuildAndProbeHint(t *testing.T) {
var (
input []string
output []struct {
SQL string
Plan []string
Result []string
Warning []string
}
)
planSuiteData := core.GetPlanSuiteData()
planSuiteData.LoadTestCases(t, &input, &output)
store := testkit.CreateMockStore(t)
tk := testkit.NewTestKit(t, store)
tk.MustExec("use test")
tk.MustExec("drop table if exists t1, t2")
tk.MustExec("create table t1(a int primary key, b int not null)")
tk.MustExec("create table t2(a int primary key, b int not null)")
tk.MustExec("insert into t1 values(1,1),(2,2)")
tk.MustExec("insert into t2 values(1,1),(2,1)")

for i, ts := range input {
testdata.OnRecord(func() {
output[i].SQL = ts
output[i].Plan = testdata.ConvertRowsToStrings(tk.MustQuery("explain format = 'brief' " + ts).Rows())
output[i].Result = testdata.ConvertRowsToStrings(tk.MustQuery(ts).Sort().Rows())
output[i].Warning = testdata.ConvertRowsToStrings(tk.MustQuery("show warnings").Rows())
})
tk.MustQuery("explain format = 'brief' " + ts).Check(testkit.Rows(output[i].Plan...))
tk.MustQuery(ts).Sort().Check(testkit.Rows(output[i].Result...))
}
}

func TestMPPSinglePartitionType(t *testing.T) {
var (
input []string
Expand Down
10 changes: 10 additions & 0 deletions planner/core/planbuilder.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,8 @@ type tableHintInfo struct {
limitHints limitHintInfo
MergeHints MergeHintInfo
leadingJoinOrder []hintTableInfo
hjBuildTables []hintTableInfo
hjProbeTables []hintTableInfo
}

type limitHintInfo struct {
Expand Down Expand Up @@ -217,6 +219,14 @@ func (info *tableHintInfo) ifPreferHashJoin(tableNames ...*hintTableInfo) bool {
return info.matchTableName(tableNames, info.hashJoinTables)
}

func (info *tableHintInfo) ifPreferHJBuild(tableNames ...*hintTableInfo) bool {
return info.matchTableName(tableNames, info.hjBuildTables)
}

func (info *tableHintInfo) ifPreferHJProbe(tableNames ...*hintTableInfo) bool {
return info.matchTableName(tableNames, info.hjProbeTables)
}

func (info *tableHintInfo) ifPreferINLJ(tableNames ...*hintTableInfo) bool {
return info.matchTableName(tableNames, info.indexNestedLoopJoinTables.inljTables)
}
Expand Down
13 changes: 13 additions & 0 deletions planner/core/testdata/plan_suite_in.json
Original file line number Diff line number Diff line change
Expand Up @@ -828,5 +828,18 @@
"select exists(select /*+ SEMI_JOIN_REWRITE() */ * from t t1 where t1.a=t.a) from t",
"select * from t where exists (select /*+ SEMI_JOIN_REWRITE() */ 1 from t t1 where t1.a > t.a)"
]
},
{
"name": "TestHJBuildAndProbeHint",
"cases": [
"select /*+ hash_join_build(t2) */ t1.a, t2.a from t1 left join t2 on t1.a=t2.a and t1.b=t2.b",
"select /*+ hash_join_probe(t2) */ t1.a, t2.a from t1 left join t2 on t1.a=t2.a and t1.b=t2.b",
"select /*+ hash_join_build(t1) */ t1.a, t2.a from t1 left join t2 on t1.a=t2.a and t1.b=t2.b",
"select /*+ hash_join_probe(t1) */ t1.a, t2.a from t1 left join t2 on t1.a=t2.a and t1.b=t2.b",
"select /*+ hash_join_build(t2) */ t1.a, t2.a from t1 join t2 on t1.a=t2.a and t1.b=t2.b",
"select /*+ hash_join_probe(t2) */ t1.a, t2.a from t1 join t2 on t1.a=t2.a and t1.b=t2.b",
"select /*+ hash_join_build(t1) */ t1.a, t2.a from t1 join t2 on t1.a=t2.a and t1.b=t2.b",
"select /*+ hash_join_probe(t1) */ t1.a, t2.a from t1 join t2 on t1.a=t2.a and t1.b=t2.b"
]
}
]
Loading

0 comments on commit 57c4938

Please sign in to comment.