Skip to content

Commit 138562f

Browse files
authored
planner: support no_hash_join hint on optimizer (#45538) (#45635)
ref #45520
1 parent 96f6214 commit 138562f

File tree

8 files changed

+263
-68
lines changed

8 files changed

+263
-68
lines changed

planner/core/casetest/rule/BUILD.bazel

Whitespace-only changes.

planner/core/casetest/rule_join_reorder_test.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,17 @@ func TestStraightJoinHint(t *testing.T) {
6161
runJoinReorderTestData(t, tk, "TestStraightJoinHint")
6262
}
6363

64+
func TestNoHashJoinHint(t *testing.T) {
65+
store := testkit.CreateMockStore(t)
66+
tk := testkit.NewTestKit(t, store)
67+
tk.MustExec("use test")
68+
tk.MustExec("create table t1(a int, b int, key(a));")
69+
tk.MustExec("create table t2(a int, b int, key(a));")
70+
tk.MustExec("create table t3(a int, b int, key(a));")
71+
tk.MustExec("create table t4(a int, b int, key(a));")
72+
runJoinReorderTestData(t, tk, "TestNoHashJoinHint")
73+
}
74+
6475
func TestLeadingJoinHint(t *testing.T) {
6576
store := testkit.CreateMockStore(t)
6677

planner/core/casetest/testdata/join_reorder_suite_in.json

Lines changed: 19 additions & 66 deletions
Large diffs are not rendered by default.

planner/core/casetest/testdata/join_reorder_suite_out.json

Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -583,6 +583,205 @@
583583
}
584584
]
585585
},
586+
{
587+
"Name": "TestNoHashJoinHint",
588+
"Cases": [
589+
{
590+
"SQL": "select /*+ no_hash_join() */ * from t1, t2",
591+
"Plan": [
592+
"HashJoin 100000000.00 root CARTESIAN inner join",
593+
"├─TableReader(Build) 10000.00 root data:TableFullScan",
594+
"│ └─TableFullScan 10000.00 cop[tikv] table:t2 keep order:false, stats:pseudo",
595+
"└─TableReader(Probe) 10000.00 root data:TableFullScan",
596+
" └─TableFullScan 10000.00 cop[tikv] table:t1 keep order:false, stats:pseudo"
597+
],
598+
"Warning": [
599+
"Warning 1815 Hint no_hash_join() is inapplicable. Please specify the table names in the arguments."
600+
]
601+
},
602+
{
603+
"SQL": "select /*+ no_hash_join(t1), hash_join(t1) */ * from t1, t2",
604+
"Plan": [
605+
"HashJoin 100000000.00 root CARTESIAN inner join",
606+
"├─TableReader(Build) 10000.00 root data:TableFullScan",
607+
"│ └─TableFullScan 10000.00 cop[tikv] table:t2 keep order:false, stats:pseudo",
608+
"└─TableReader(Probe) 10000.00 root data:TableFullScan",
609+
" └─TableFullScan 10000.00 cop[tikv] table:t1 keep order:false, stats:pseudo"
610+
],
611+
"Warning": [
612+
"Warning 1815 Some HASH_JOIN and NO_HASH_JOIN hints conflict, NO_HASH_JOIN is ignored"
613+
]
614+
},
615+
{
616+
"SQL": "select /*+ no_hash_join(t1), hash_join(t2) */ * from t1, t2",
617+
"Plan": [
618+
"HashJoin 100000000.00 root CARTESIAN inner join",
619+
"├─TableReader(Build) 10000.00 root data:TableFullScan",
620+
"│ └─TableFullScan 10000.00 cop[tikv] table:t2 keep order:false, stats:pseudo",
621+
"└─TableReader(Probe) 10000.00 root data:TableFullScan",
622+
" └─TableFullScan 10000.00 cop[tikv] table:t1 keep order:false, stats:pseudo"
623+
],
624+
"Warning": [
625+
"Warning 1815 Some HASH_JOIN and NO_HASH_JOIN hints conflict, NO_HASH_JOIN is ignored"
626+
]
627+
},
628+
{
629+
"SQL": "select /*+ no_hash_join(t1) */ * from t1, t2",
630+
"Plan": [
631+
"MergeJoin 100000000.00 root inner join",
632+
"├─TableReader(Build) 10000.00 root data:TableFullScan",
633+
"│ └─TableFullScan 10000.00 cop[tikv] table:t2 keep order:false, stats:pseudo",
634+
"└─TableReader(Probe) 10000.00 root data:TableFullScan",
635+
" └─TableFullScan 10000.00 cop[tikv] table:t1 keep order:false, stats:pseudo"
636+
],
637+
"Warning": null
638+
},
639+
{
640+
"SQL": "select /*+ no_hash_join(t1, t2) */ * from t1, t2",
641+
"Plan": [
642+
"MergeJoin 100000000.00 root inner join",
643+
"├─TableReader(Build) 10000.00 root data:TableFullScan",
644+
"│ └─TableFullScan 10000.00 cop[tikv] table:t2 keep order:false, stats:pseudo",
645+
"└─TableReader(Probe) 10000.00 root data:TableFullScan",
646+
" └─TableFullScan 10000.00 cop[tikv] table:t1 keep order:false, stats:pseudo"
647+
],
648+
"Warning": null
649+
},
650+
{
651+
"SQL": "select /*+ no_hash_join(t1) */ * from t1, t2 where t1.a=t2.a",
652+
"Plan": [
653+
"IndexHashJoin 12487.50 root inner join, inner:IndexLookUp, outer key:test.t1.a, inner key:test.t2.a, equal cond:eq(test.t1.a, test.t2.a)",
654+
"├─TableReader(Build) 9990.00 root data:Selection",
655+
"│ └─Selection 9990.00 cop[tikv] not(isnull(test.t1.a))",
656+
"│ └─TableFullScan 10000.00 cop[tikv] table:t1 keep order:false, stats:pseudo",
657+
"└─IndexLookUp(Probe) 12487.50 root ",
658+
" ├─Selection(Build) 12487.50 cop[tikv] not(isnull(test.t2.a))",
659+
" │ └─IndexRangeScan 12500.00 cop[tikv] table:t2, index:a(a) range: decided by [eq(test.t2.a, test.t1.a)], keep order:false, stats:pseudo",
660+
" └─TableRowIDScan(Probe) 12487.50 cop[tikv] table:t2 keep order:false, stats:pseudo"
661+
],
662+
"Warning": null
663+
},
664+
{
665+
"SQL": "select /*+ no_hash_join(t1, t2) */ * from t1, t2 where t1.b=t2.b",
666+
"Plan": [
667+
"MergeJoin 12487.50 root inner join, left key:test.t1.b, right key:test.t2.b",
668+
"├─Sort(Build) 9990.00 root test.t2.b",
669+
"│ └─TableReader 9990.00 root data:Selection",
670+
"│ └─Selection 9990.00 cop[tikv] not(isnull(test.t2.b))",
671+
"│ └─TableFullScan 10000.00 cop[tikv] table:t2 keep order:false, stats:pseudo",
672+
"└─Sort(Probe) 9990.00 root test.t1.b",
673+
" └─TableReader 9990.00 root data:Selection",
674+
" └─Selection 9990.00 cop[tikv] not(isnull(test.t1.b))",
675+
" └─TableFullScan 10000.00 cop[tikv] table:t1 keep order:false, stats:pseudo"
676+
],
677+
"Warning": null
678+
},
679+
{
680+
"SQL": "select /*+ no_hash_join(t1) */ * from t1, t2 where t1.a=t2.a and t1.b=t2.b",
681+
"Plan": [
682+
"IndexHashJoin 12475.01 root inner join, inner:IndexLookUp, outer key:test.t1.a, inner key:test.t2.a, equal cond:eq(test.t1.a, test.t2.a), eq(test.t1.b, test.t2.b)",
683+
"├─TableReader(Build) 9980.01 root data:Selection",
684+
"│ └─Selection 9980.01 cop[tikv] not(isnull(test.t1.a)), not(isnull(test.t1.b))",
685+
"│ └─TableFullScan 10000.00 cop[tikv] table:t1 keep order:false, stats:pseudo",
686+
"└─IndexLookUp(Probe) 12475.01 root ",
687+
" ├─Selection(Build) 12487.50 cop[tikv] not(isnull(test.t2.a))",
688+
" │ └─IndexRangeScan 12500.00 cop[tikv] table:t2, index:a(a) range: decided by [eq(test.t2.a, test.t1.a)], keep order:false, stats:pseudo",
689+
" └─Selection(Probe) 12475.01 cop[tikv] not(isnull(test.t2.b))",
690+
" └─TableRowIDScan 12487.50 cop[tikv] table:t2 keep order:false, stats:pseudo"
691+
],
692+
"Warning": null
693+
},
694+
{
695+
"SQL": "select /*+ no_hash_join(t2) */ * from t1 left join t2 on t1.b=t2.b",
696+
"Plan": [
697+
"MergeJoin 12487.50 root left outer join, left key:test.t1.b, right key:test.t2.b",
698+
"├─Sort(Build) 9990.00 root test.t2.b",
699+
"│ └─TableReader 9990.00 root data:Selection",
700+
"│ └─Selection 9990.00 cop[tikv] not(isnull(test.t2.b))",
701+
"│ └─TableFullScan 10000.00 cop[tikv] table:t2 keep order:false, stats:pseudo",
702+
"└─Sort(Probe) 10000.00 root test.t1.b",
703+
" └─TableReader 10000.00 root data:TableFullScan",
704+
" └─TableFullScan 10000.00 cop[tikv] table:t1 keep order:false, stats:pseudo"
705+
],
706+
"Warning": null
707+
},
708+
{
709+
"SQL": "select /*+ no_hash_join(t2) */ * from t1 left join t2 on t1.a=t2.a",
710+
"Plan": [
711+
"IndexHashJoin 12487.50 root left outer join, inner:IndexLookUp, outer key:test.t1.a, inner key:test.t2.a, equal cond:eq(test.t1.a, test.t2.a)",
712+
"├─TableReader(Build) 10000.00 root data:TableFullScan",
713+
"│ └─TableFullScan 10000.00 cop[tikv] table:t1 keep order:false, stats:pseudo",
714+
"└─IndexLookUp(Probe) 12487.50 root ",
715+
" ├─Selection(Build) 12487.50 cop[tikv] not(isnull(test.t2.a))",
716+
" │ └─IndexRangeScan 12500.00 cop[tikv] table:t2, index:a(a) range: decided by [eq(test.t2.a, test.t1.a)], keep order:false, stats:pseudo",
717+
" └─TableRowIDScan(Probe) 12487.50 cop[tikv] table:t2 keep order:false, stats:pseudo"
718+
],
719+
"Warning": null
720+
},
721+
{
722+
"SQL": "select /*+ no_hash_join(t2) */ * from t1 right join t2 on t1.b=t2.b",
723+
"Plan": [
724+
"MergeJoin 12487.50 root right outer join, left key:test.t1.b, right key:test.t2.b",
725+
"├─Sort(Build) 9990.00 root test.t1.b",
726+
"│ └─TableReader 9990.00 root data:Selection",
727+
"│ └─Selection 9990.00 cop[tikv] not(isnull(test.t1.b))",
728+
"│ └─TableFullScan 10000.00 cop[tikv] table:t1 keep order:false, stats:pseudo",
729+
"└─Sort(Probe) 10000.00 root test.t2.b",
730+
" └─TableReader 10000.00 root data:TableFullScan",
731+
" └─TableFullScan 10000.00 cop[tikv] table:t2 keep order:false, stats:pseudo"
732+
],
733+
"Warning": null
734+
},
735+
{
736+
"SQL": "select /*+ no_hash_join(t2) */ * from t1 right join t2 on t1.a=t2.a",
737+
"Plan": [
738+
"IndexHashJoin 12487.50 root right outer join, inner:IndexLookUp, outer key:test.t2.a, inner key:test.t1.a, equal cond:eq(test.t2.a, test.t1.a)",
739+
"├─TableReader(Build) 10000.00 root data:TableFullScan",
740+
"│ └─TableFullScan 10000.00 cop[tikv] table:t2 keep order:false, stats:pseudo",
741+
"└─IndexLookUp(Probe) 12487.50 root ",
742+
" ├─Selection(Build) 12487.50 cop[tikv] not(isnull(test.t1.a))",
743+
" │ └─IndexRangeScan 12500.00 cop[tikv] table:t1, index:a(a) range: decided by [eq(test.t1.a, test.t2.a)], keep order:false, stats:pseudo",
744+
" └─TableRowIDScan(Probe) 12487.50 cop[tikv] table:t1 keep order:false, stats:pseudo"
745+
],
746+
"Warning": null
747+
},
748+
{
749+
"SQL": "select /*+ leading(t4, t3, t2, t1), no_hash_join(t2, t3) */ * from t1, t2, t3, t4",
750+
"Plan": [
751+
"Projection 10000000000000000.00 root test.t1.a, test.t1.b, test.t2.a, test.t2.b, test.t3.a, test.t3.b, test.t4.a, test.t4.b",
752+
"└─HashJoin 10000000000000000.00 root CARTESIAN inner join",
753+
" ├─TableReader(Build) 10000.00 root data:TableFullScan",
754+
" │ └─TableFullScan 10000.00 cop[tikv] table:t1 keep order:false, stats:pseudo",
755+
" └─MergeJoin(Probe) 1000000000000.00 root inner join",
756+
" ├─TableReader(Build) 10000.00 root data:TableFullScan",
757+
" │ └─TableFullScan 10000.00 cop[tikv] table:t2 keep order:false, stats:pseudo",
758+
" └─MergeJoin(Probe) 100000000.00 root inner join",
759+
" ├─TableReader(Build) 10000.00 root data:TableFullScan",
760+
" │ └─TableFullScan 10000.00 cop[tikv] table:t3 keep order:false, stats:pseudo",
761+
" └─TableReader(Probe) 10000.00 root data:TableFullScan",
762+
" └─TableFullScan 10000.00 cop[tikv] table:t4 keep order:false, stats:pseudo"
763+
],
764+
"Warning": null
765+
},
766+
{
767+
"SQL": "select /*+ leading(t1, t2, t3, t4), hash_join(t1, t2), no_hash_join(t3), hash_join(t4) */ * from t1, t2, t3, t4",
768+
"Plan": [
769+
"HashJoin 10000000000000000.00 root CARTESIAN inner join",
770+
"├─TableReader(Build) 10000.00 root data:TableFullScan",
771+
"│ └─TableFullScan 10000.00 cop[tikv] table:t4 keep order:false, stats:pseudo",
772+
"└─MergeJoin(Probe) 1000000000000.00 root inner join",
773+
" ├─TableReader(Build) 10000.00 root data:TableFullScan",
774+
" │ └─TableFullScan 10000.00 cop[tikv] table:t3 keep order:false, stats:pseudo",
775+
" └─HashJoin(Probe) 100000000.00 root CARTESIAN inner join",
776+
" ├─TableReader(Build) 10000.00 root data:TableFullScan",
777+
" │ └─TableFullScan 10000.00 cop[tikv] table:t2 keep order:false, stats:pseudo",
778+
" └─TableReader(Probe) 10000.00 root data:TableFullScan",
779+
" └─TableFullScan 10000.00 cop[tikv] table:t1 keep order:false, stats:pseudo"
780+
],
781+
"Warning": null
782+
}
783+
]
784+
},
586785
{
587786
"Name": "TestLeadingJoinHint",
588787
"Cases": [

planner/core/exhaust_physical_plans.go

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,8 @@ func (p *LogicalJoin) GetMergeJoin(prop *property.PhysicalProperty, schema *expr
231231
}
232232
// If TiDB_SMJ hint is existed, it should consider enforce merge join,
233233
// because we can't trust lhsChildProperty completely.
234-
if (p.preferJoinType & preferMergeJoin) > 0 {
234+
if (p.preferJoinType&preferMergeJoin) > 0 ||
235+
(p.preferJoinType&preferNoHashJoin) > 0 { // if hash join is not allowed, generate as many other types of join as possible to avoid 'cant-find-plan' error.
235236
joins = append(joins, p.getEnforcedMergeJoin(prop, schema, statsInfo)...)
236237
}
237238

@@ -389,6 +390,7 @@ func (p *LogicalJoin) getHashJoins(prop *property.PhysicalProperty) (joins []Phy
389390
forceLeftToBuild = false
390391
forceRightToBuild = false
391392
}
393+
392394
joins = make([]PhysicalPlan, 0, 2)
393395
switch p.JoinType {
394396
case SemiJoin, AntiSemiJoin, LeftOuterSemiJoin, AntiLeftOuterSemiJoin:
@@ -435,7 +437,15 @@ func (p *LogicalJoin) getHashJoins(prop *property.PhysicalProperty) (joins []Phy
435437
}
436438
}
437439
}
440+
438441
forced = (p.preferJoinType&preferHashJoin > 0) || forceLeftToBuild || forceRightToBuild
442+
noHashJoin := (p.preferJoinType & preferNoHashJoin) > 0
443+
if !forced && noHashJoin {
444+
return nil, false
445+
} else if forced && noHashJoin {
446+
p.ctx.GetSessionVars().StmtCtx.AppendWarning(ErrInternal.GenWithStack(
447+
"Some HASH_JOIN and NO_HASH_JOIN hints conflict, NO_HASH_JOIN is ignored"))
448+
}
439449
return
440450
}
441451

planner/core/logical_plan_builder.go

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,8 @@ const (
9696
HintINLMJ = "inl_merge_join"
9797
// TiDBHashJoin is hint enforce hash join.
9898
TiDBHashJoin = "tidb_hj"
99+
// HintNoHashJoin is the hint to enforce the query not to use hash join.
100+
HintNoHashJoin = "no_hash_join"
99101
// HintHJ is hint enforce hash join.
100102
HintHJ = "hash_join"
101103
// HintHashJoinBuild is hint enforce hash join's build side
@@ -625,6 +627,14 @@ func (p *LogicalJoin) setPreferredJoinTypeAndOrder(hintInfo *tableHintInfo) {
625627
p.preferJoinType |= preferHashJoin
626628
p.rightPreferJoinType |= preferHashJoin
627629
}
630+
if hintInfo.ifPreferNoHashJoin(lhsAlias) {
631+
p.preferJoinType |= preferNoHashJoin
632+
p.leftPreferJoinType |= preferNoHashJoin
633+
}
634+
if hintInfo.ifPreferNoHashJoin(rhsAlias) {
635+
p.preferJoinType |= preferNoHashJoin
636+
p.rightPreferJoinType |= preferNoHashJoin
637+
}
628638
if hintInfo.ifPreferINLJ(lhsAlias) {
629639
p.preferJoinType |= preferLeftAsINLJInner
630640
p.leftPreferJoinType |= preferINLJ
@@ -3710,6 +3720,7 @@ func (b *PlanBuilder) pushTableHints(hints []*ast.TableOptimizerHint, currentLev
37103720
hints = b.hintProcessor.GetCurrentStmtHints(hints, currentLevel)
37113721
var (
37123722
sortMergeTables, inljTables, inlhjTables, inlmjTables, hashJoinTables, bcTables []hintTableInfo
3723+
noHashJoinTables []hintTableInfo
37133724
shuffleJoinTables []hintTableInfo
37143725
indexHintList, indexMergeHintList []indexHintInfo
37153726
tiflashTables, tikvTables []hintTableInfo
@@ -3724,7 +3735,7 @@ func (b *PlanBuilder) pushTableHints(hints []*ast.TableOptimizerHint, currentLev
37243735
for _, hint := range hints {
37253736
// Set warning for the hint that requires the table name.
37263737
switch hint.HintName.L {
3727-
case TiDBMergeJoin, HintSMJ, TiDBIndexNestedLoopJoin, HintINLJ, HintINLHJ, HintINLMJ,
3738+
case TiDBMergeJoin, HintSMJ, TiDBIndexNestedLoopJoin, HintINLJ, HintINLHJ, HintINLMJ, HintNoHashJoin,
37283739
TiDBHashJoin, HintHJ, HintUseIndex, HintIgnoreIndex, HintForceIndex, HintOrderIndex, HintNoOrderIndex, HintIndexMerge, HintLeading:
37293740
if len(hint.Tables) == 0 {
37303741
b.pushHintWithoutTableWarning(hint)
@@ -3747,6 +3758,8 @@ func (b *PlanBuilder) pushTableHints(hints []*ast.TableOptimizerHint, currentLev
37473758
inlmjTables = append(inlmjTables, tableNames2HintTableInfo(b.ctx, hint.HintName.L, hint.Tables, b.hintProcessor, currentLevel)...)
37483759
case TiDBHashJoin, HintHJ:
37493760
hashJoinTables = append(hashJoinTables, tableNames2HintTableInfo(b.ctx, hint.HintName.L, hint.Tables, b.hintProcessor, currentLevel)...)
3761+
case HintNoHashJoin:
3762+
noHashJoinTables = append(noHashJoinTables, tableNames2HintTableInfo(b.ctx, hint.HintName.L, hint.Tables, b.hintProcessor, currentLevel)...)
37503763
case HintMPP1PhaseAgg:
37513764
aggHints.preferAggType |= preferMPP1PhaseAgg
37523765
case HintMPP2PhaseAgg:
@@ -3857,6 +3870,7 @@ func (b *PlanBuilder) pushTableHints(hints []*ast.TableOptimizerHint, currentLev
38573870
shuffleJoinTables: shuffleJoinTables,
38583871
indexNestedLoopJoinTables: indexNestedLoopJoinTables{inljTables, inlhjTables, inlmjTables},
38593872
hashJoinTables: hashJoinTables,
3873+
noHashJoinTables: noHashJoinTables,
38603874
indexHintList: indexHintList,
38613875
tiflashTables: tiflashTables,
38623876
tikvTables: tikvTables,
@@ -7062,6 +7076,8 @@ func getInnerFromParenthesesAndUnaryPlus(expr ast.ExprNode) ast.ExprNode {
70627076
// containDifferentJoinTypes checks whether `preferJoinType` contains different
70637077
// join types.
70647078
func containDifferentJoinTypes(preferJoinType uint) bool {
7079+
preferJoinType &= ^preferNoHashJoin
7080+
70657081
inlMask := preferRightAsINLJInner ^ preferLeftAsINLJInner
70667082
inlhjMask := preferRightAsINLHJInner ^ preferLeftAsINLHJInner
70677083
inlmjMask := preferRightAsINLMJInner ^ preferLeftAsINLMJInner

planner/core/logical_plans.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ const (
118118
preferHJBuild
119119
preferHJProbe
120120
preferHashJoin
121+
preferNoHashJoin
121122
preferMergeJoin
122123
preferBCJoin
123124
preferShuffleJoin

planner/core/planbuilder.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ type tableHintInfo struct {
9393
broadcastJoinTables []hintTableInfo
9494
shuffleJoinTables []hintTableInfo
9595
hashJoinTables []hintTableInfo
96+
noHashJoinTables []hintTableInfo
9697
indexHintList []indexHintInfo
9798
tiflashTables []hintTableInfo
9899
tikvTables []hintTableInfo
@@ -237,6 +238,10 @@ func (info *tableHintInfo) ifPreferHashJoin(tableNames ...*hintTableInfo) bool {
237238
return info.matchTableName(tableNames, info.hashJoinTables)
238239
}
239240

241+
func (info *tableHintInfo) ifPreferNoHashJoin(tableNames ...*hintTableInfo) bool {
242+
return info.matchTableName(tableNames, info.noHashJoinTables)
243+
}
244+
240245
func (info *tableHintInfo) ifPreferHJBuild(tableNames ...*hintTableInfo) bool {
241246
return info.matchTableName(tableNames, info.hjBuildTables)
242247
}

0 commit comments

Comments
 (0)