diff --git a/cmd/explaintest/r/generated_columns.result b/cmd/explaintest/r/generated_columns.result index 0d046859cf92e..4cd0f39e02d11 100644 --- a/cmd/explaintest/r/generated_columns.result +++ b/cmd/explaintest/r/generated_columns.result @@ -71,7 +71,7 @@ VALUES ('{"a": 1}', '{"1": "1"}'); ANALYZE TABLE sgc1, sgc2; EXPLAIN SELECT /*+ TIDB_INLJ(sgc1, sgc2) */ * from sgc1 join sgc2 on sgc1.a=sgc2.a; id count task operator info -IndexHashJoin_35 5.00 root inner join, inner:IndexLookUp_25, outer key:Column#8, inner key:Column#3 +IndexJoin_26 5.00 root inner join, inner:IndexLookUp_25, outer key:Column#8, inner key:Column#3 ├─IndexLookUp_25 5.00 root │ ├─Selection_24 5.00 cop[tikv] not(isnull(Column#3)) │ │ └─IndexScan_22 5.00 cop[tikv] table:sgc1, index:a, range: decided by [eq(Column#3, Column#8)], keep order:false diff --git a/cmd/explaintest/r/index_join.result b/cmd/explaintest/r/index_join.result index 6cafbfe8b3dd6..68c53dbe4f809 100644 --- a/cmd/explaintest/r/index_join.result +++ b/cmd/explaintest/r/index_join.result @@ -8,7 +8,7 @@ set session tidb_hashagg_partial_concurrency = 1; set session tidb_hashagg_final_concurrency = 1; explain select /*+ TIDB_INLJ(t1, t2) */ * from t1 join t2 on t1.a=t2.a; id count task operator info -IndexHashJoin_34 5.00 root inner join, inner:IndexLookUp_24, outer key:Column#4, inner key:Column#1 +IndexJoin_25 5.00 root inner join, inner:IndexLookUp_24, outer key:Column#4, inner key:Column#1 ├─IndexLookUp_24 5.00 root │ ├─Selection_23 5.00 cop[tikv] not(isnull(Column#1)) │ │ └─IndexScan_21 5.00 cop[tikv] table:t1, index:a, range: decided by [eq(Column#1, Column#4)], keep order:false @@ -32,11 +32,11 @@ create table t2(a int not null, b int not null, key a(a)); set @@tidb_opt_insubq_to_join_and_agg=0; explain select /*+ TIDB_INLJ(t2@sel_2) */ * from t1 where t1.a in (select t2.a from t2); id count task operator info -IndexMergeJoin_14 8000.00 root semi join, inner:IndexReader_12, outer key:Column#1, inner key:Column#4 +IndexJoin_10 8000.00 root semi join, inner:IndexReader_9, outer key:Column#1, inner key:Column#4 ├─TableReader_18 10000.00 root data:TableScan_17 │ └─TableScan_17 10000.00 cop[tikv] table:t1, range:[-inf,+inf], keep order:false, stats:pseudo -└─IndexReader_12 1.25 root index:IndexScan_11 - └─IndexScan_11 1.25 cop[tikv] table:t2, index:a, range: decided by [eq(Column#4, Column#1)], keep order:true, stats:pseudo +└─IndexReader_9 1.25 root index:IndexScan_8 + └─IndexScan_8 1.25 cop[tikv] table:t2, index:a, range: decided by [eq(Column#4, Column#1)], keep order:false, stats:pseudo show warnings; Level Code Message set @@tidb_opt_insubq_to_join_and_agg=1; diff --git a/cmd/explaintest/r/topn_push_down.result b/cmd/explaintest/r/topn_push_down.result index 4ca18ae558037..97101d3dcfad2 100644 --- a/cmd/explaintest/r/topn_push_down.result +++ b/cmd/explaintest/r/topn_push_down.result @@ -217,20 +217,20 @@ create table t(a int not null, index idx(a)); explain select /*+ TIDB_INLJ(t2) */ * from t t1 join t t2 on t1.a = t2.a limit 5; id count task operator info Limit_11 5.00 root offset:0, count:5 -└─IndexMergeJoin_19 5.00 root inner join, inner:IndexReader_17, outer key:Column#1, inner key:Column#3 +└─IndexJoin_15 5.00 root inner join, inner:IndexReader_14, outer key:Column#1, inner key:Column#3 ├─TableReader_23 4.00 root data:TableScan_22 │ └─TableScan_22 4.00 cop[tikv] table:t1, range:[-inf,+inf], keep order:false, stats:pseudo - └─IndexReader_17 1.25 root index:IndexScan_16 - └─IndexScan_16 1.25 cop[tikv] table:t2, index:a, range: decided by [eq(Column#3, Column#1)], keep order:true, stats:pseudo + └─IndexReader_14 1.25 root index:IndexScan_13 + └─IndexScan_13 1.25 cop[tikv] table:t2, index:a, range: decided by [eq(Column#3, Column#1)], keep order:false, stats:pseudo explain select /*+ TIDB_INLJ(t2) */ * from t t1 left join t t2 on t1.a = t2.a where t2.a is null limit 5; id count task operator info Limit_12 5.00 root offset:0, count:5 └─Selection_13 5.00 root isnull(Column#3) - └─IndexMergeJoin_21 5.00 root left outer join, inner:IndexReader_19, outer key:Column#1, inner key:Column#3 + └─IndexJoin_17 5.00 root left outer join, inner:IndexReader_16, outer key:Column#1, inner key:Column#3 ├─TableReader_25 4.00 root data:TableScan_24 │ └─TableScan_24 4.00 cop[tikv] table:t1, range:[-inf,+inf], keep order:false, stats:pseudo - └─IndexReader_19 1.25 root index:IndexScan_18 - └─IndexScan_18 1.25 cop[tikv] table:t2, index:a, range: decided by [eq(Column#3, Column#1)], keep order:true, stats:pseudo + └─IndexReader_16 1.25 root index:IndexScan_15 + └─IndexScan_15 1.25 cop[tikv] table:t2, index:a, range: decided by [eq(Column#3, Column#1)], keep order:false, stats:pseudo explain select /*+ TIDB_SMJ(t1, t2) */ * from t t1 join t t2 on t1.a = t2.a limit 5; id count task operator info Limit_11 5.00 root offset:0, count:5 diff --git a/executor/index_lookup_join_test.go b/executor/index_lookup_join_test.go index f7352f35d47c9..2a28a6fe90bf9 100644 --- a/executor/index_lookup_join_test.go +++ b/executor/index_lookup_join_test.go @@ -89,6 +89,16 @@ func (s *testSuite1) TestInapplicableIndexJoinHint(c *C) { tk.MustQuery(`select /*+ TIDB_INLJ(t1, t2) */ * from t1 join t2 on t1.a=t2.a;`).Check(testkit.Rows()) tk.MustQuery(`show warnings;`).Check(testkit.Rows(`Warning 1815 Optimizer Hint /*+ INL_JOIN(t1, t2) */ or /*+ TIDB_INLJ(t1, t2) */ is inapplicable`)) + tk.MustQuery(`select /*+ INL_HASH_JOIN(t1, t2) */ * from t1, t2;`).Check(testkit.Rows()) + tk.MustQuery(`show warnings;`).Check(testkit.Rows(`Warning 1815 Optimizer Hint /*+ INL_HASH_JOIN(t1, t2) */ is inapplicable without column equal ON condition`)) + tk.MustQuery(`select /*+ INL_HASH_JOIN(t1, t2) */ * from t1 join t2 on t1.a=t2.a;`).Check(testkit.Rows()) + tk.MustQuery(`show warnings;`).Check(testkit.Rows(`Warning 1815 Optimizer Hint /*+ INL_HASH_JOIN(t1, t2) */ is inapplicable`)) + + tk.MustQuery(`select /*+ INL_MERGE_JOIN(t1, t2) */ * from t1, t2;`).Check(testkit.Rows()) + tk.MustQuery(`show warnings;`).Check(testkit.Rows(`Warning 1815 Optimizer Hint /*+ INL_MERGE_JOIN(t1, t2) */ is inapplicable without column equal ON condition`)) + tk.MustQuery(`select /*+ INL_MERGE_JOIN(t1, t2) */ * from t1 join t2 on t1.a=t2.a;`).Check(testkit.Rows()) + tk.MustQuery(`show warnings;`).Check(testkit.Rows(`Warning 1815 Optimizer Hint /*+ INL_MERGE_JOIN(t1, t2) */ is inapplicable`)) + tk.MustExec(`drop table if exists t1, t2;`) tk.MustExec(`create table t1(a bigint, b bigint, index idx_a(a));`) tk.MustExec(`create table t2(a bigint, b bigint);`) @@ -96,6 +106,16 @@ func (s *testSuite1) TestInapplicableIndexJoinHint(c *C) { tk.MustQuery(`show warnings;`).Check(testkit.Rows(`Warning 1815 Optimizer Hint /*+ INL_JOIN(t1) */ or /*+ TIDB_INLJ(t1) */ is inapplicable`)) tk.MustQuery(`select /*+ TIDB_INLJ(t2) */ * from t1 right join t2 on t1.a=t2.a;`).Check(testkit.Rows()) tk.MustQuery(`show warnings;`).Check(testkit.Rows(`Warning 1815 Optimizer Hint /*+ INL_JOIN(t2) */ or /*+ TIDB_INLJ(t2) */ is inapplicable`)) + + tk.MustQuery(`select /*+ INL_HASH_JOIN(t1) */ * from t1 left join t2 on t1.a=t2.a;`).Check(testkit.Rows()) + tk.MustQuery(`show warnings;`).Check(testkit.Rows(`Warning 1815 Optimizer Hint /*+ INL_HASH_JOIN(t1) */ is inapplicable`)) + tk.MustQuery(`select /*+ INL_HASH_JOIN(t2) */ * from t1 right join t2 on t1.a=t2.a;`).Check(testkit.Rows()) + tk.MustQuery(`show warnings;`).Check(testkit.Rows(`Warning 1815 Optimizer Hint /*+ INL_HASH_JOIN(t2) */ is inapplicable`)) + + tk.MustQuery(`select /*+ INL_MERGE_JOIN(t1) */ * from t1 left join t2 on t1.a=t2.a;`).Check(testkit.Rows()) + tk.MustQuery(`show warnings;`).Check(testkit.Rows(`Warning 1815 Optimizer Hint /*+ INL_MERGE_JOIN(t1) */ is inapplicable`)) + tk.MustQuery(`select /*+ INL_MERGE_JOIN(t2) */ * from t1 right join t2 on t1.a=t2.a;`).Check(testkit.Rows()) + tk.MustQuery(`show warnings;`).Check(testkit.Rows(`Warning 1815 Optimizer Hint /*+ INL_MERGE_JOIN(t2) */ is inapplicable`)) } func (s *testSuite) TestIndexJoinOverflow(c *C) { diff --git a/executor/join_test.go b/executor/join_test.go index 6f24ecb01aaba..426d0744abd3c 100644 --- a/executor/join_test.go +++ b/executor/join_test.go @@ -205,9 +205,9 @@ func (s *testSuiteJoin2) TestJoin(c *C) { tk.MustExec("create table t1(a int, b int)") tk.MustExec("insert into t values(1, 3), (2, 2), (3, 1)") tk.MustExec("insert into t1 values(0, 0), (1, 2), (1, 3), (3, 4)") - tk.MustQuery("select /*+ TIDB_INLJ(t1) */ * from t join t1 on t.a=t1.a order by t.b").Check(testkit.Rows("3 1 3 4", "1 3 1 2", "1 3 1 3")) + tk.MustQuery("select /*+ TIDB_INLJ(t1) */ * from t join t1 on t.a=t1.a order by t.b").Sort().Check(testkit.Rows("1 3 1 2", "1 3 1 3", "3 1 3 4")) tk.MustQuery("select /*+ TIDB_INLJ(t) */ t.a, t.b from t join t1 on t.a=t1.a where t1.b = 4 limit 1").Check(testkit.Rows("3 1")) - tk.MustQuery("select /*+ TIDB_INLJ(t, t1) */ * from t right join t1 on t.a=t1.a order by t.b").Check(testkit.Rows(" 0 0", "3 1 3 4", "1 3 1 2", "1 3 1 3")) + tk.MustQuery("select /*+ TIDB_INLJ(t, t1) */ * from t right join t1 on t.a=t1.a order by t.b").Sort().Check(testkit.Rows("1 3 1 2", "1 3 1 3", "3 1 3 4", " 0 0")) // join reorder will disorganize the resulting schema tk.MustExec("drop table if exists t, t1") @@ -1020,16 +1020,16 @@ func (s *testSuiteJoin1) TestIndexLookupJoin(c *C) { tk.MustExec("insert into t1 values(1, 0), (2, null)") tk.MustExec("create table t2(a int primary key)") tk.MustExec("insert into t2 values(0)") - tk.MustQuery("select /*+ TIDB_INLJ(t2)*/ * from t1 left join t2 on t1.b = t2.a;").Check(testkit.Rows( - `2 `, + tk.MustQuery("select /*+ TIDB_INLJ(t2)*/ * from t1 left join t2 on t1.b = t2.a;").Sort().Check(testkit.Rows( `1 0 0`, + `2 `, )) tk.MustExec("create table t3(a int, key(a))") tk.MustExec("insert into t3 values(0)") tk.MustQuery("select /*+ TIDB_INLJ(t3)*/ * from t1 left join t3 on t1.b = t3.a;").Check(testkit.Rows( - `2 `, `1 0 0`, + `2 `, )) } @@ -1054,14 +1054,14 @@ func (s *testSuiteJoin1) TestIndexNestedLoopHashJoin(c *C) { tk.MustExec("analyze table t") tk.MustExec("analyze table s") // Test IndexNestedLoopHashJoin keepOrder. - tk.MustQuery("explain select /*+ TIDB_INLJ(s) */ * from t left join s on t.a=s.a order by t.pk").Check(testkit.Rows( + tk.MustQuery("explain select /*+ INL_HASH_JOIN(s) */ * from t left join s on t.a=s.a order by t.pk").Check(testkit.Rows( "IndexHashJoin_28 100.00 root left outer join, inner:TableReader_22, outer key:Column#2, inner key:Column#3", "├─TableReader_30 100.00 root data:TableScan_29", "│ └─TableScan_29 100.00 cop[tikv] table:t, range:[-inf,+inf], keep order:true", "└─TableReader_22 1.00 root data:TableScan_21", " └─TableScan_21 1.00 cop[tikv] table:s, range: decided by [Column#2], keep order:false", )) - rs := tk.MustQuery("select /*+ TIDB_INLJ(s) */ * from t left join s on t.a=s.a order by t.pk") + rs := tk.MustQuery("select /*+ INL_HASH_JOIN(s) */ * from t left join s on t.a=s.a order by t.pk") for i, row := range rs.Rows() { c.Assert(row[0].(string), Equals, fmt.Sprintf("%d", i)) } diff --git a/planner/core/exhaust_physical_plans.go b/planner/core/exhaust_physical_plans.go index 4f126bff4bf9f..edc1336ee3a4e 100644 --- a/planner/core/exhaust_physical_plans.go +++ b/planner/core/exhaust_physical_plans.go @@ -1157,18 +1157,45 @@ func (ijHelper *indexJoinBuildHelper) buildTemplateRange(matchedKeyCnt int, eqAn // 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, forced bool) { - rightOuter := (p.preferJoinType & preferLeftAsIndexInner) > 0 - leftOuter := (p.preferJoinType & preferRightAsIndexInner) > 0 - hasIndexJoinHint := leftOuter || rightOuter + inljRightOuter := (p.preferJoinType & preferLeftAsINLJInner) > 0 + inljLeftOuter := (p.preferJoinType & preferRightAsINLJInner) > 0 + hasINLJHint := inljLeftOuter || inljRightOuter + + inlhjRightOuter := (p.preferJoinType & preferLeftAsINLHJInner) > 0 + inlhjLeftOuter := (p.preferJoinType & preferRightAsINLHJInner) > 0 + hasINLHJHint := inlhjLeftOuter || inlhjRightOuter + + inlmjRightOuter := (p.preferJoinType & preferLeftAsINLMJInner) > 0 + inlmjLeftOuter := (p.preferJoinType & preferRightAsINLMJInner) > 0 + hasINLMJHint := inlmjLeftOuter || inlmjRightOuter + + forceLeftOuter := inljLeftOuter || inlhjLeftOuter || inlmjLeftOuter + forceRightOuter := inljRightOuter || inlhjRightOuter || inlmjRightOuter defer func() { - if !forced && hasIndexJoinHint { + // refine error message + if !forced && (hasINLJHint || hasINLHJHint || hasINLMJHint) { // Construct warning message prefix. - errMsg := "Optimizer Hint INL_JOIN or TIDB_INLJ is inapplicable" + 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 { - errMsg = fmt.Sprintf("Optimizer Hint %s or %s is inapplicable", - restore2JoinHint(HintINLJ, p.hintInfo.indexNestedLoopJoinTables), - restore2JoinHint(TiDBIndexNestedLoopJoin, p.hintInfo.indexNestedLoopJoinTables)) + t := p.hintInfo.indexNestedLoopJoinTables + switch { + case len(t.inljTables) != 0: + errMsg = fmt.Sprintf("Optimizer Hint %s or %s is inapplicable", + restore2JoinHint(HintINLJ, t.inljTables), restore2JoinHint(TiDBIndexNestedLoopJoin, t.inljTables)) + case len(t.inlhjTables) != 0: + errMsg = fmt.Sprintf("Optimizer Hint %s is inapplicable", restore2JoinHint(HintINLHJ, t.inlhjTables)) + case len(t.inlmjTables) != 0: + errMsg = fmt.Sprintf("Optimizer Hint %s is inapplicable", restore2JoinHint(HintINLMJ, t.inlmjTables)) + } } // Append inapplicable reason. @@ -1182,36 +1209,93 @@ func (p *LogicalJoin) tryToGetIndexJoin(prop *property.PhysicalProperty) (indexJ } }() + // 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 switch p.JoinType { case SemiJoin, AntiSemiJoin, LeftOuterSemiJoin, AntiLeftOuterSemiJoin, LeftOuterJoin: - join := p.getIndexJoinByOuterIdx(prop, 0) - return join, join != nil && leftOuter + supportLeftOuter = true case RightOuterJoin: - join := p.getIndexJoinByOuterIdx(prop, 1) - return join, join != nil && rightOuter + supportRightOuter = true case InnerJoin: - lhsCardinality := p.Children()[0].statsInfo().Count() - rhsCardinality := p.Children()[1].statsInfo().Count() - - leftJoins := p.getIndexJoinByOuterIdx(prop, 0) - if leftJoins != nil && (leftOuter && !rightOuter || lhsCardinality < rhsCardinality) { - return leftJoins, leftOuter + supportLeftOuter, supportRightOuter = true, true + } + + var allLeftOuterJoins, allRightOuterJoins, forcedLeftOuterJoins, forcedRightOuterJoins []PhysicalPlan + if supportLeftOuter { + allLeftOuterJoins = p.getIndexJoinByOuterIdx(prop, 0) + forcedLeftOuterJoins = make([]PhysicalPlan, 0, len(allLeftOuterJoins)) + for _, j := range allLeftOuterJoins { + switch j.(type) { + case *PhysicalIndexJoin: + if hasINLJHint { + forcedLeftOuterJoins = append(forcedLeftOuterJoins, j) + } + case *PhysicalIndexHashJoin: + if hasINLHJHint { + forcedLeftOuterJoins = append(forcedLeftOuterJoins, j) + } + case *PhysicalIndexMergeJoin: + if hasINLMJHint { + forcedLeftOuterJoins = append(forcedLeftOuterJoins, j) + } + } } - - rightJoins := p.getIndexJoinByOuterIdx(prop, 1) - if rightJoins != nil && (rightOuter && !leftOuter || rhsCardinality < lhsCardinality) { - return rightJoins, rightOuter + switch { + case p.JoinType == InnerJoin && p.Children()[0].statsInfo().Count() < p.Children()[1].statsInfo().Count(): + if len(forcedLeftOuterJoins) != 0 { + return forcedLeftOuterJoins, forceLeftOuter + } + if len(allLeftOuterJoins) != 0 { + return allLeftOuterJoins, forceLeftOuter + } + case len(forcedLeftOuterJoins) == 0 && !supportRightOuter: + return allLeftOuterJoins, false + case len(forcedLeftOuterJoins) != 0 && (!supportRightOuter || forceLeftOuter && !forceRightOuter): + return forcedLeftOuterJoins, forceLeftOuter + } + } + if supportRightOuter { + allRightOuterJoins = p.getIndexJoinByOuterIdx(prop, 1) + forcedRightOuterJoins = make([]PhysicalPlan, 0, len(allRightOuterJoins)) + for _, j := range allRightOuterJoins { + switch j.(type) { + case *PhysicalIndexJoin: + if hasINLJHint { + forcedRightOuterJoins = append(forcedRightOuterJoins, j) + } + case *PhysicalIndexHashJoin: + if hasINLHJHint { + forcedRightOuterJoins = append(forcedRightOuterJoins, j) + } + case *PhysicalIndexMergeJoin: + if hasINLMJHint { + forcedRightOuterJoins = append(forcedRightOuterJoins, j) + } + } + } + switch { + case p.JoinType == InnerJoin && p.Children()[0].statsInfo().Count() > p.Children()[1].statsInfo().Count(): + if len(forcedRightOuterJoins) != 0 { + return forcedRightOuterJoins, forceRightOuter + } + if len(allRightOuterJoins) != 0 { + return allRightOuterJoins, forceRightOuter + } + case len(forcedRightOuterJoins) == 0 && !supportLeftOuter: + return allRightOuterJoins, false + case len(forcedRightOuterJoins) != 0 && (!supportLeftOuter || forceRightOuter && !forceLeftOuter): + return forcedRightOuterJoins, forceRightOuter } - - canForceLeft := leftJoins != nil && leftOuter - canForceRight := rightJoins != nil && rightOuter - forced = canForceLeft || canForceRight - - joins := append(leftJoins, rightJoins...) - return joins, forced } - return nil, false + canForceLeft := len(forcedLeftOuterJoins) != 0 && forceLeftOuter + canForceRight := len(forcedRightOuterJoins) != 0 && forceRightOuter + forced = canForceLeft || canForceRight + if forced { + return append(forcedLeftOuterJoins, forcedRightOuterJoins...), forced + } + return append(allLeftOuterJoins, allRightOuterJoins...), forced } // LogicalJoin can generates hash join, index join and sort merge join. diff --git a/planner/core/hints.go b/planner/core/hints.go index a19a89404a652..6def3a392ff7d 100644 --- a/planner/core/hints.go +++ b/planner/core/hints.go @@ -317,7 +317,9 @@ func genHintsFromPhysicalPlan(p PhysicalPlan, nodeType nodeType) (res []*ast.Tab case *PhysicalIndexJoin: res = append(res, getJoinHints(p.SCtx(), HintINLJ, p.SelectBlockOffset(), nodeType, pp.children[pp.InnerChildIdx])...) case *PhysicalIndexMergeJoin: - res = append(res, getJoinHints(p.SCtx(), HintINLJ, p.SelectBlockOffset(), nodeType, pp.children[pp.InnerChildIdx])...) + res = append(res, getJoinHints(p.SCtx(), HintINLMJ, p.SelectBlockOffset(), nodeType, pp.children[pp.InnerChildIdx])...) + case *PhysicalIndexHashJoin: + res = append(res, getJoinHints(p.SCtx(), HintINLHJ, p.SelectBlockOffset(), nodeType, pp.children[pp.InnerChildIdx])...) } return res } diff --git a/planner/core/logical_plan_builder.go b/planner/core/logical_plan_builder.go index e57201c08bcae..c1ed2e4ab1c73 100644 --- a/planner/core/logical_plan_builder.go +++ b/planner/core/logical_plan_builder.go @@ -57,6 +57,10 @@ const ( TiDBIndexNestedLoopJoin = "tidb_inlj" // HintINLJ is hint enforce index nested loop join. HintINLJ = "inl_join" + // HintINLHJ is hint enforce index nested loop hash join. + HintINLHJ = "inl_hash_join" + // HintINLMJ is hint enforce index nested loop merge join. + HintINLMJ = "inl_merge_join" // TiDBHashJoin is hint enforce hash join. TiDBHashJoin = "tidb_hj" // HintHJ is hint enforce hash join. @@ -381,10 +385,22 @@ func (p *LogicalJoin) setPreferredJoinType(hintInfo *tableHintInfo) { p.preferJoinType |= preferHashJoin } if hintInfo.ifPreferINLJ(lhsAlias) { - p.preferJoinType |= preferLeftAsIndexInner + p.preferJoinType |= preferLeftAsINLJInner } if hintInfo.ifPreferINLJ(rhsAlias) { - p.preferJoinType |= preferRightAsIndexInner + p.preferJoinType |= preferRightAsINLJInner + } + if hintInfo.ifPreferINLHJ(lhsAlias) { + p.preferJoinType |= preferLeftAsINLHJInner + } + if hintInfo.ifPreferINLHJ(rhsAlias) { + p.preferJoinType |= preferRightAsINLHJInner + } + if hintInfo.ifPreferINLMJ(lhsAlias) { + p.preferJoinType |= preferLeftAsINLMJInner + } + if hintInfo.ifPreferINLMJ(rhsAlias) { + p.preferJoinType |= preferRightAsINLMJInner } // set hintInfo for further usage if this hint info can be used. @@ -392,9 +408,7 @@ func (p *LogicalJoin) setPreferredJoinType(hintInfo *tableHintInfo) { p.hintInfo = hintInfo } - // If there're multiple join types and one of them is not index join hint, - // then there is a conflict of join types. - if bits.OnesCount(p.preferJoinType) > 1 && (p.preferJoinType^preferRightAsIndexInner^preferLeftAsIndexInner) > 0 { + if containDifferentJoinTypes(p.preferJoinType) { errMsg := "Join hints are conflict, you can only specify one type of join" warning := ErrInternal.GenWithStack(errMsg) p.ctx.GetSessionVars().StmtCtx.AppendWarning(warning) @@ -2085,10 +2099,10 @@ func (b *PlanBuilder) unfoldWildStar(p LogicalPlan, selectFields []*ast.SelectFi func (b *PlanBuilder) pushTableHints(hints []*ast.TableOptimizerHint, nodeType nodeType, currentLevel int) { hints = b.hintProcessor.getCurrentStmtHints(hints, nodeType, currentLevel) var ( - sortMergeTables, INLJTables, hashJoinTables []hintTableInfo - indexHintList []indexHintInfo - tiflashTables []hintTableInfo - aggHints aggHintInfo + sortMergeTables, INLJTables, INLHJTables, INLMJTables, hashJoinTables []hintTableInfo + indexHintList []indexHintInfo + tiflashTables []hintTableInfo + aggHints aggHintInfo ) for _, hint := range hints { switch hint.HintName.L { @@ -2096,6 +2110,10 @@ func (b *PlanBuilder) pushTableHints(hints []*ast.TableOptimizerHint, nodeType n sortMergeTables = append(sortMergeTables, tableNames2HintTableInfo(b.ctx, hint.Tables, b.hintProcessor, nodeType, currentLevel)...) case TiDBIndexNestedLoopJoin, HintINLJ: INLJTables = append(INLJTables, tableNames2HintTableInfo(b.ctx, hint.Tables, b.hintProcessor, nodeType, currentLevel)...) + case HintINLHJ: + INLHJTables = append(INLHJTables, tableNames2HintTableInfo(b.ctx, hint.Tables, b.hintProcessor, nodeType, currentLevel)...) + case HintINLMJ: + INLMJTables = append(INLMJTables, tableNames2HintTableInfo(b.ctx, hint.Tables, b.hintProcessor, nodeType, currentLevel)...) case TiDBHashJoin, HintHJ: hashJoinTables = append(hashJoinTables, tableNames2HintTableInfo(b.ctx, hint.Tables, b.hintProcessor, nodeType, currentLevel)...) case HintHashAgg: @@ -2146,7 +2164,7 @@ func (b *PlanBuilder) pushTableHints(hints []*ast.TableOptimizerHint, nodeType n } b.tableHintInfo = append(b.tableHintInfo, tableHintInfo{ sortMergeJoinTables: sortMergeTables, - indexNestedLoopJoinTables: INLJTables, + indexNestedLoopJoinTables: indexNestedLoopJoinTables{INLJTables, INLHJTables, INLMJTables}, hashJoinTables: hashJoinTables, indexHintList: indexHintList, flashTables: tiflashTables, @@ -2156,7 +2174,9 @@ func (b *PlanBuilder) pushTableHints(hints []*ast.TableOptimizerHint, nodeType n func (b *PlanBuilder) popTableHints() { hintInfo := b.tableHintInfo[len(b.tableHintInfo)-1] - b.appendUnmatchedJoinHintWarning(HintINLJ, TiDBIndexNestedLoopJoin, hintInfo.indexNestedLoopJoinTables) + b.appendUnmatchedJoinHintWarning(HintINLJ, TiDBIndexNestedLoopJoin, hintInfo.indexNestedLoopJoinTables.inljTables) + b.appendUnmatchedJoinHintWarning(HintINLHJ, "", hintInfo.indexNestedLoopJoinTables.inlhjTables) + b.appendUnmatchedJoinHintWarning(HintINLMJ, "", hintInfo.indexNestedLoopJoinTables.inlmjTables) b.appendUnmatchedJoinHintWarning(HintSMJ, TiDBMergeJoin, hintInfo.sortMergeJoinTables) b.appendUnmatchedJoinHintWarning(HintHJ, TiDBHashJoin, hintInfo.hashJoinTables) b.tableHintInfo = b.tableHintInfo[:len(b.tableHintInfo)-1] @@ -2167,8 +2187,12 @@ func (b *PlanBuilder) appendUnmatchedJoinHintWarning(joinType string, joinTypeAl if len(unMatchedTables) == 0 { return } - errMsg := fmt.Sprintf("There are no matching table names for (%s) in optimizer hint %s or %s. Maybe you can use the table alias name", - strings.Join(unMatchedTables, ", "), restore2JoinHint(joinType, hintTables), restore2JoinHint(joinTypeAlias, hintTables)) + if len(joinTypeAlias) != 0 { + joinTypeAlias = fmt.Sprintf(" or %s", restore2JoinHint(joinTypeAlias, hintTables)) + } + + errMsg := fmt.Sprintf("There are no matching table names for (%s) in optimizer hint %s%s. Maybe you can use the table alias name", + strings.Join(unMatchedTables, ", "), restore2JoinHint(joinType, hintTables), joinTypeAlias) b.ctx.GetSessionVars().StmtCtx.AppendWarning(ErrInternal.GenWithStack(errMsg)) } @@ -2743,7 +2767,13 @@ func (b *PlanBuilder) buildSemiJoin(outerPlan, innerPlan LogicalPlan, onConditio joinPlan.preferJoinType |= preferHashJoin } if b.TableHints().ifPreferINLJ(innerAlias) { - joinPlan.preferJoinType = preferRightAsIndexInner + joinPlan.preferJoinType = preferRightAsINLJInner + } + if b.TableHints().ifPreferINLHJ(innerAlias) { + joinPlan.preferJoinType = preferRightAsINLHJInner + } + if b.TableHints().ifPreferINLMJ(innerAlias) { + joinPlan.preferJoinType = preferRightAsINLMJInner } // If there're multiple join hints, they're conflict. if bits.OnesCount(joinPlan.preferJoinType) > 1 { @@ -3904,3 +3934,29 @@ func getInnerFromParenthesesAndUnaryPlus(expr ast.ExprNode) ast.ExprNode { } return expr } + +// containDifferentJoinTypes checks whether `preferJoinType` contains different +// join types. +func containDifferentJoinTypes(preferJoinType uint) bool { + inlMask := preferRightAsINLJInner ^ preferLeftAsINLJInner + inlhjMask := preferRightAsINLHJInner ^ preferLeftAsINLHJInner + inlmjMask := preferRightAsINLMJInner ^ preferLeftAsINLMJInner + + mask := inlMask ^ inlhjMask ^ inlmjMask + onesCount := bits.OnesCount(preferJoinType & ^mask) + if onesCount > 1 || onesCount == 1 && preferJoinType&mask > 0 { + return true + } + + cnt := 0 + if preferJoinType&inlMask > 0 { + cnt++ + } + if preferJoinType&inlhjMask > 0 { + cnt++ + } + if preferJoinType&inlmjMask > 0 { + cnt++ + } + return cnt > 1 +} diff --git a/planner/core/logical_plans.go b/planner/core/logical_plans.go index 29abcf399e1fd..3a787d413b348 100644 --- a/planner/core/logical_plans.go +++ b/planner/core/logical_plans.go @@ -97,8 +97,12 @@ func (tp JoinType) String() string { } const ( - preferLeftAsIndexInner = 1 << iota - preferRightAsIndexInner + preferLeftAsINLJInner uint = 1 << iota + preferRightAsINLJInner + preferLeftAsINLHJInner + preferRightAsINLHJInner + preferLeftAsINLMJInner + preferRightAsINLMJInner preferHashJoin preferMergeJoin preferHashAgg diff --git a/planner/core/physical_plan_test.go b/planner/core/physical_plan_test.go index acc4f0bd1c8a1..9ebd2bf29e6ba 100644 --- a/planner/core/physical_plan_test.go +++ b/planner/core/physical_plan_test.go @@ -20,6 +20,7 @@ import ( "github.com/pingcap/parser" "github.com/pingcap/parser/model" "github.com/pingcap/parser/terror" + "github.com/pingcap/tidb/domain" "github.com/pingcap/tidb/infoschema" "github.com/pingcap/tidb/kv" "github.com/pingcap/tidb/planner" @@ -1072,3 +1073,43 @@ func testDAGPlanBuilderSplitAvg(c *C, root core.PhysicalPlan) { testDAGPlanBuilderSplitAvg(c, son) } } + +func (s *testPlanSuite) TestIndexJoinHint(c *C) { + defer testleak.AfterTest(c)() + store, dom, err := newStoreWithBootstrap() + c.Assert(err, IsNil) + defer func() { + dom.Close() + store.Close() + }() + se, err := session.CreateSession4Test(store) + c.Assert(err, IsNil) + ctx := context.Background() + _, err = se.Execute(ctx, "use test") + c.Assert(err, IsNil) + _, err = se.Execute(ctx, `drop table if exists test.t1, test.t2;`) + c.Assert(err, IsNil) + _, err = se.Execute(ctx, `create table test.t1(a bigint, b bigint, index idx_a(a), index idx_b(b));`) + c.Assert(err, IsNil) + _, err = se.Execute(ctx, `create table test.t2(a bigint, b bigint, index idx_a(a), index idx_b(b));`) + c.Assert(err, IsNil) + var input []string + var output []struct { + SQL string + Plan string + } + is := domain.GetDomain(se).InfoSchema() + s.testData.GetTestCases(c, &input, &output) + for i, tt := range input { + comment := Commentf("case:%v sql: %s", i, tt) + stmt, err := s.ParseOneStmt(tt, "", "") + c.Assert(err, IsNil, comment) + p, _, err := planner.Optimize(ctx, se, stmt, is) + c.Assert(err, IsNil, comment) + s.testData.OnRecord(func() { + output[i].SQL = tt + output[i].Plan = core.ToString(p) + }) + c.Assert(core.ToString(p), Equals, output[i].Plan, comment) + } +} diff --git a/planner/core/planbuilder.go b/planner/core/planbuilder.go index 9c397252da117..35b8ed6593982 100644 --- a/planner/core/planbuilder.go +++ b/planner/core/planbuilder.go @@ -56,13 +56,19 @@ type visitInfo struct { err error } +type indexNestedLoopJoinTables struct { + inljTables []hintTableInfo + inlhjTables []hintTableInfo + inlmjTables []hintTableInfo +} + type tableHintInfo struct { - indexNestedLoopJoinTables []hintTableInfo - sortMergeJoinTables []hintTableInfo - hashJoinTables []hintTableInfo - indexHintList []indexHintInfo - flashTables []hintTableInfo - aggHints aggHintInfo + indexNestedLoopJoinTables + sortMergeJoinTables []hintTableInfo + hashJoinTables []hintTableInfo + indexHintList []indexHintInfo + flashTables []hintTableInfo + aggHints aggHintInfo } type hintTableInfo struct { @@ -108,7 +114,15 @@ func (info *tableHintInfo) ifPreferHashJoin(tableNames ...*hintTableInfo) bool { } func (info *tableHintInfo) ifPreferINLJ(tableNames ...*hintTableInfo) bool { - return info.matchTableName(tableNames, info.indexNestedLoopJoinTables) + return info.matchTableName(tableNames, info.indexNestedLoopJoinTables.inljTables) +} + +func (info *tableHintInfo) ifPreferINLHJ(tableNames ...*hintTableInfo) bool { + return info.matchTableName(tableNames, info.indexNestedLoopJoinTables.inlhjTables) +} + +func (info *tableHintInfo) ifPreferINLMJ(tableNames ...*hintTableInfo) bool { + return info.matchTableName(tableNames, info.indexNestedLoopJoinTables.inlmjTables) } func (info *tableHintInfo) ifPreferTiFlash(tableNames ...*hintTableInfo) bool { diff --git a/planner/core/testdata/analyze_suite_out.json b/planner/core/testdata/analyze_suite_out.json index 7edd75001b329..22bc60ca4e7ef 100644 --- a/planner/core/testdata/analyze_suite_out.json +++ b/planner/core/testdata/analyze_suite_out.json @@ -142,13 +142,13 @@ "explain select /*+ TIDB_INLJ(t2) */ * from t1 join t2 on t2.a=t1.a and t2.b>t1.b-1 and t2.bIndexMergeJoin{TableReader(Table(t))->IndexReader(Index(t.c_d_e)[[NULL,+inf]])}(Column#13,Column#27)}(Column#1,Column#13)->Projection" + "Best": "MergeInnerJoin{TableReader(Table(t))->IndexJoin{TableReader(Table(t))->IndexReader(Index(t.c_d_e)[[NULL,+inf]])}(Column#13,Column#27)}(Column#1,Column#13)->Projection" }, { "SQL": "select /*+ SM_JOIN(test.t1) */ t1.a, t1.b from t t1, (select /*+ INL_JOIN(test.t3) */ t2.a from t t2, t t3 where t2.a = t3.c) s where t1.a=s.a", - "Best": "MergeInnerJoin{TableReader(Table(t))->IndexMergeJoin{TableReader(Table(t))->IndexReader(Index(t.c_d_e)[[NULL,+inf]])}(Column#13,Column#27)}(Column#1,Column#13)->Projection" + "Best": "MergeInnerJoin{TableReader(Table(t))->IndexJoin{TableReader(Table(t))->IndexReader(Index(t.c_d_e)[[NULL,+inf]])}(Column#13,Column#27)}(Column#1,Column#13)->Projection" }, { "SQL": "select /*+ SM_JOIN(t1) */ t1.a, t1.b from t t1, (select /*+ HASH_JOIN(t2) */ t2.a from t t2, t t3 where t2.a = t3.c) s where t1.a=s.a", @@ -16,15 +16,15 @@ }, { "SQL": "select /*+ INL_JOIN(t1) */ t1.a, t1.b from t t1, (select /*+ HASH_JOIN(t2) */ t2.a from t t2, t t3 where t2.a = t3.c) s where t1.a=s.a", - "Best": "IndexMergeJoin{TableReader(Table(t))->LeftHashJoin{IndexReader(Index(t.f)[[NULL,+inf]])->IndexReader(Index(t.c_d_e)[[NULL,+inf]])}(Column#13,Column#27)}(Column#13,Column#1)->Projection" + "Best": "IndexJoin{TableReader(Table(t))->LeftHashJoin{IndexReader(Index(t.f)[[NULL,+inf]])->IndexReader(Index(t.c_d_e)[[NULL,+inf]])}(Column#13,Column#27)}(Column#13,Column#1)->Projection" }, { "SQL": "select /*+ INL_JOIN(test.t1) */ t1.a, t1.b from t t1, (select /*+ HASH_JOIN(test.t2) */ t2.a from t t2, t t3 where t2.a = t3.c) s where t1.a=s.a", - "Best": "IndexMergeJoin{TableReader(Table(t))->LeftHashJoin{IndexReader(Index(t.f)[[NULL,+inf]])->IndexReader(Index(t.c_d_e)[[NULL,+inf]])}(Column#13,Column#27)}(Column#13,Column#1)->Projection" + "Best": "IndexJoin{TableReader(Table(t))->LeftHashJoin{IndexReader(Index(t.f)[[NULL,+inf]])->IndexReader(Index(t.c_d_e)[[NULL,+inf]])}(Column#13,Column#27)}(Column#13,Column#1)->Projection" }, { "SQL": "select /*+ INL_JOIN(t1) */ t1.a, t1.b from t t1, (select /*+ SM_JOIN(t2) */ t2.a from t t2, t t3 where t2.a = t3.c) s where t1.a=s.a", - "Best": "IndexMergeJoin{TableReader(Table(t))->MergeInnerJoin{TableReader(Table(t))->IndexReader(Index(t.c_d_e)[[NULL,+inf]])}(Column#13,Column#27)}(Column#13,Column#1)->Projection" + "Best": "IndexJoin{TableReader(Table(t))->MergeInnerJoin{TableReader(Table(t))->IndexReader(Index(t.c_d_e)[[NULL,+inf]])}(Column#13,Column#27)}(Column#13,Column#1)->Projection" }, { "SQL": "select /*+ HASH_JOIN(t1) */ t1.a, t1.b from t t1, (select /*+ SM_JOIN(t2) */ t2.a from t t2, t t3 where t2.a = t3.c) s where t1.a=s.a", @@ -36,7 +36,7 @@ }, { "SQL": "select /*+ HASH_JOIN(t1) */ t1.a, t1.b from t t1, (select /*+ INL_JOIN(t2) */ t2.a from t t2, t t3 where t2.a = t3.c) s where t1.a=s.a", - "Best": "RightHashJoin{TableReader(Table(t))->IndexMergeJoin{TableReader(Table(t))->IndexReader(Index(t.c_d_e)[[NULL,+inf]])}(Column#27,Column#13)}(Column#1,Column#13)->Projection" + "Best": "RightHashJoin{TableReader(Table(t))->IndexJoin{TableReader(Table(t))->IndexReader(Index(t.c_d_e)[[NULL,+inf]])}(Column#27,Column#13)}(Column#1,Column#13)->Projection" }, { "SQL": "select /*+ SM_JOIN(t1) */ t1.a, t1.b from t t1, (select t2.a from t t2, t t3 where t2.a = t3.c) s where t1.a=s.a", @@ -44,7 +44,7 @@ }, { "SQL": "select /*+ INL_JOIN(t1) */ t1.a, t1.b from t t1, (select t2.a from t t2, t t3 where t2.a = t3.c) s where t1.a=s.a", - "Best": "IndexMergeJoin{TableReader(Table(t))->LeftHashJoin{IndexReader(Index(t.f)[[NULL,+inf]])->IndexReader(Index(t.c_d_e)[[NULL,+inf]])}(Column#13,Column#27)}(Column#13,Column#1)->Projection" + "Best": "IndexJoin{TableReader(Table(t))->LeftHashJoin{IndexReader(Index(t.f)[[NULL,+inf]])->IndexReader(Index(t.c_d_e)[[NULL,+inf]])}(Column#13,Column#27)}(Column#13,Column#1)->Projection" }, { "SQL": "select /*+ HASH_JOIN(t1) */ t1.a, t1.b from t t1, (select t2.a from t t2, t t3 where t2.a = t3.c) s where t1.a=s.a", @@ -439,15 +439,15 @@ }, { "SQL": "select /*+ TIDB_INLJ(t1, t2) */ * from t t1, t t2 where t1.a = t2.a", - "Best": "IndexMergeJoin{TableReader(Table(t))->TableReader(Table(t))}(Column#1,Column#13)" + "Best": "IndexJoin{TableReader(Table(t))->TableReader(Table(t))}(Column#1,Column#13)" }, { "SQL": "select /*+ TIDB_INLJ(t2) */ * from t t1, t t2 where t1.a = t2.c", - "Best": "IndexMergeJoin{TableReader(Table(t))->IndexLookUp(Index(t.c_d_e)[[NULL,+inf]], Table(t))}(Column#1,Column#15)" + "Best": "IndexJoin{TableReader(Table(t))->IndexLookUp(Index(t.c_d_e)[[NULL,+inf]], Table(t))}(Column#1,Column#15)" }, { "SQL": "select /*+ TIDB_INLJ(t2) */ t1.a , t2.a from t t1, t t2 where t1.a = t2.c", - "Best": "IndexMergeJoin{IndexReader(Index(t.f)[[NULL,+inf]])->IndexReader(Index(t.c_d_e)[[NULL,+inf]])}(Column#1,Column#15)->Projection" + "Best": "IndexJoin{IndexReader(Index(t.f)[[NULL,+inf]])->IndexReader(Index(t.c_d_e)[[NULL,+inf]])}(Column#1,Column#15)->Projection" }, { "SQL": "select /*+ TIDB_INLJ(t1, t2) */ t1.a, t2.a from t t1, t t2 where t1.a = t2.a order by t1.c", @@ -459,15 +459,15 @@ }, { "SQL": "select /*+ TIDB_INLJ(t1) */ t1.a , t2.a from t t1, t t2 where t1.a = t2.c", - "Best": "IndexMergeJoin{TableReader(Table(t))->IndexReader(Index(t.c_d_e)[[NULL,+inf]])}(Column#15,Column#1)->Projection" + "Best": "IndexJoin{TableReader(Table(t))->IndexReader(Index(t.c_d_e)[[NULL,+inf]])}(Column#15,Column#1)->Projection" }, { "SQL": "select /*+ TIDB_INLJ(t1, t2) */ * from t t1 left outer join t t2 on t1.a = t2.a and t2.b < 1", - "Best": "IndexMergeJoin{TableReader(Table(t))->TableReader(Table(t)->Sel([lt(Column#14, 1)]))}(Column#1,Column#13)" + "Best": "IndexJoin{TableReader(Table(t))->TableReader(Table(t)->Sel([lt(Column#14, 1)]))}(Column#1,Column#13)" }, { "SQL": "select /*+ TIDB_INLJ(t1, t2) */ * from t t1 join t t2 on t1.d=t2.d and t2.c = 1", - "Best": "IndexMergeJoin{TableReader(Table(t))->IndexLookUp(Index(t.c_d_e)[[NULL,+inf]], Table(t))}(Column#4,Column#16)" + "Best": "IndexJoin{TableReader(Table(t))->IndexLookUp(Index(t.c_d_e)[[NULL,+inf]], Table(t))}(Column#4,Column#16)" }, { "SQL": "select /*+ TIDB_INLJ(t1, t2) */ * from t t1 left outer join t t2 on t1.a = t2.b", @@ -483,27 +483,27 @@ }, { "SQL": "select /*+ TIDB_INLJ(t1) */ * from t t1 where t1.a in (select a from t t2)", - "Best": "IndexMergeJoin{TableReader(Table(t))->IndexReader(Index(t.f)[[NULL,+inf]])}(Column#13,Column#1)->Projection" + "Best": "IndexJoin{TableReader(Table(t))->IndexReader(Index(t.f)[[NULL,+inf]])}(Column#13,Column#1)->Projection" }, { "SQL": "select /*+ TIDB_INLJ(t2) */ * from t t1 join t t2 where t1.c=t2.c and t1.f=t2.f", - "Best": "IndexMergeJoin{TableReader(Table(t))->IndexLookUp(Index(t.c_d_e)[[NULL,+inf]], Table(t))}(Column#3,Column#15)" + "Best": "IndexJoin{TableReader(Table(t))->IndexLookUp(Index(t.c_d_e)[[NULL,+inf]], Table(t))}(Column#3,Column#15)" }, { "SQL": "select /*+ TIDB_INLJ(t2) */ * from t t1 join t t2 where t1.a = t2.a and t1.f=t2.f", - "Best": "IndexMergeJoin{TableReader(Table(t))->TableReader(Table(t))}(Column#1,Column#13)" + "Best": "IndexJoin{TableReader(Table(t))->TableReader(Table(t))}(Column#1,Column#13)" }, { "SQL": "select /*+ TIDB_INLJ(t2) */ * from t t1 join t t2 where t1.f=t2.f and t1.a=t2.a", - "Best": "IndexMergeJoin{TableReader(Table(t))->TableReader(Table(t))}(Column#1,Column#13)" + "Best": "IndexJoin{TableReader(Table(t))->TableReader(Table(t))}(Column#1,Column#13)" }, { "SQL": "select /*+ TIDB_INLJ(t2) */ * from t t1 join t t2 where t1.a=t2.a and t2.a in (1, 2)", - "Best": "IndexMergeJoin{TableReader(Table(t))->TableReader(Table(t)->Sel([in(Column#13, 1, 2)]))}(Column#1,Column#13)" + "Best": "IndexJoin{TableReader(Table(t))->TableReader(Table(t)->Sel([in(Column#13, 1, 2)]))}(Column#1,Column#13)" }, { "SQL": "select /*+ TIDB_INLJ(t2) */ * from t t1 join t t2 where t1.b=t2.c and t1.b=1 and t2.d > t1.d-10 and t2.d < t1.d+10", - "Best": "IndexHashJoin{TableReader(Table(t)->Sel([eq(Column#2, 1)]))->IndexLookUp(Index(t.c_d_e)[[NULL,+inf]], Table(t))}" + "Best": "IndexJoin{TableReader(Table(t)->Sel([eq(Column#2, 1)]))->IndexLookUp(Index(t.c_d_e)[[NULL,+inf]], Table(t))}" }, { "SQL": "select /*+ TIDB_INLJ(t2) */ * from t t1 join t t2 where t1.b=t2.b and t1.c=1 and t2.c=1 and t2.d > t1.d-10 and t2.d < t1.d+10", @@ -613,7 +613,7 @@ }, { "SQL": "delete /*+ TIDB_INLJ(t1, t2) */ t1 from t t1, t t2 where t1.c=t2.c", - "Best": "IndexMergeJoin{TableReader(Table(t))->IndexLookUp(Index(t.c_d_e)[[NULL,+inf]], Table(t))}(Column#3,Column#15)->Delete", + "Best": "IndexJoin{TableReader(Table(t))->IndexLookUp(Index(t.c_d_e)[[NULL,+inf]], Table(t))}(Column#3,Column#15)->Delete", "Hints": "USE_INDEX(@`del_1` `test`.`t1` ), USE_INDEX(@`del_1` `test`.`t2` `c_d_e`), INL_JOIN(@`del_1` `test`.`t2`)" }, { @@ -807,7 +807,7 @@ }, { "SQL": "select /*+ tidb_inlj(a,b) */ sum(a.g), sum(b.g) from t a join t b on a.g = b.g and a.g > 60 group by a.g order by a.g limit 1", - "Best": "IndexMergeJoin{IndexReader(Index(t.g)[(60,+inf]])->IndexReader(Index(t.g)[[NULL,+inf]]->Sel([gt(Column#22, 60)]))}(Column#10,Column#22)->Projection->StreamAgg->Limit->Projection" + "Best": "IndexJoin{IndexReader(Index(t.g)[(60,+inf]])->IndexReader(Index(t.g)[[NULL,+inf]]->Sel([gt(Column#22, 60)]))}(Column#10,Column#22)->Projection->StreamAgg->Limit->Projection" }, { "SQL": "select sum(a.g), sum(b.g) from t a join t b on a.g = b.g and a.a>5 group by a.g order by a.g limit 1", @@ -1111,13 +1111,13 @@ "Cases": [ { "SQL": "select /*+ TIDB_INLJ(t1) */ t1.a, t2.a, t3.a from t t1, t t2, t t3 where t1.a = t2.a and t2.a = t3.a;", - "Best": "RightHashJoin{IndexReader(Index(t.f)[[NULL,+inf]])->IndexMergeJoin{TableReader(Table(t))->IndexReader(Index(t.f)[[NULL,+inf]])}(Column#13,Column#1)}(Column#25,Column#13)->Projection", + "Best": "RightHashJoin{IndexReader(Index(t.f)[[NULL,+inf]])->IndexJoin{TableReader(Table(t))->IndexReader(Index(t.f)[[NULL,+inf]])}(Column#13,Column#1)}(Column#25,Column#13)->Projection", "Warning": "", "Hints": "USE_INDEX(@`sel_1` `test`.`t3` `f`), USE_INDEX(@`sel_1` `test`.`t1` ), USE_INDEX(@`sel_1` `test`.`t2` `f`), INL_JOIN(@`sel_1` `test`.`t1`), HASH_JOIN(@`sel_1` `test`.`t3`)" }, { "SQL": "select /*+ TIDB_INLJ(test.t1) */ t1.a, t2.a, t3.a from t t1, t t2, t t3 where t1.a = t2.a and t2.a = t3.a;", - "Best": "RightHashJoin{IndexReader(Index(t.f)[[NULL,+inf]])->IndexMergeJoin{TableReader(Table(t))->IndexReader(Index(t.f)[[NULL,+inf]])}(Column#13,Column#1)}(Column#25,Column#13)->Projection", + "Best": "RightHashJoin{IndexReader(Index(t.f)[[NULL,+inf]])->IndexJoin{TableReader(Table(t))->IndexReader(Index(t.f)[[NULL,+inf]])}(Column#13,Column#1)}(Column#25,Column#13)->Projection", "Warning": "", "Hints": "USE_INDEX(@`sel_1` `test`.`t3` `f`), USE_INDEX(@`sel_1` `test`.`t1` ), USE_INDEX(@`sel_1` `test`.`t2` `f`), INL_JOIN(@`sel_1` `test`.`t1`), HASH_JOIN(@`sel_1` `test`.`t3`)" }, @@ -1200,12 +1200,12 @@ "Cases": [ { "SQL": "select /*+ SM_JOIN(@sel_1 t1), INL_JOIN(@sel_2 t3) */ t1.a, t1.b from t t1, (select t2.a from t t2, t t3 where t2.a = t3.c) s where t1.a=s.a", - "Plan": "MergeInnerJoin{TableReader(Table(t))->IndexMergeJoin{TableReader(Table(t))->IndexReader(Index(t.c_d_e)[[NULL,+inf]])}(Column#13,Column#27)}(Column#1,Column#13)->Projection", + "Plan": "MergeInnerJoin{TableReader(Table(t))->IndexJoin{TableReader(Table(t))->IndexReader(Index(t.c_d_e)[[NULL,+inf]])}(Column#13,Column#27)}(Column#1,Column#13)->Projection", "Hints": "USE_INDEX(@`sel_1` `test`.`t1` ), USE_INDEX(@`sel_2` `test`.`t2` ), USE_INDEX(@`sel_2` `test`.`t3` `c_d_e`), INL_JOIN(@`sel_2` `test`.`t3`), SM_JOIN(@`sel_1` `test`.`t1`)" }, { "SQL": "select /*+ SM_JOIN(@sel_1 t1), INL_JOIN(@qb t3) */ t1.a, t1.b from t t1, (select /*+ QB_NAME(qb) */ t2.a from t t2, t t3 where t2.a = t3.c) s where t1.a=s.a", - "Plan": "MergeInnerJoin{TableReader(Table(t))->IndexMergeJoin{TableReader(Table(t))->IndexReader(Index(t.c_d_e)[[NULL,+inf]])}(Column#13,Column#27)}(Column#1,Column#13)->Projection", + "Plan": "MergeInnerJoin{TableReader(Table(t))->IndexJoin{TableReader(Table(t))->IndexReader(Index(t.c_d_e)[[NULL,+inf]])}(Column#13,Column#27)}(Column#1,Column#13)->Projection", "Hints": "USE_INDEX(@`sel_1` `test`.`t1` ), USE_INDEX(@`sel_2` `test`.`t2` ), USE_INDEX(@`sel_2` `test`.`t3` `c_d_e`), INL_JOIN(@`sel_2` `test`.`t3`), SM_JOIN(@`sel_1` `test`.`t1`)" }, { @@ -1220,12 +1220,12 @@ }, { "SQL": "select /*+ INL_JOIN(@sel_1 t1), HASH_JOIN(@sel_2 t2) */ t1.a, t1.b from t t1, (select t2.a from t t2, t t3 where t2.a = t3.c) s where t1.a=s.a", - "Plan": "IndexMergeJoin{TableReader(Table(t))->LeftHashJoin{IndexReader(Index(t.f)[[NULL,+inf]])->IndexReader(Index(t.c_d_e)[[NULL,+inf]])}(Column#13,Column#27)}(Column#13,Column#1)->Projection", + "Plan": "IndexJoin{TableReader(Table(t))->LeftHashJoin{IndexReader(Index(t.f)[[NULL,+inf]])->IndexReader(Index(t.c_d_e)[[NULL,+inf]])}(Column#13,Column#27)}(Column#13,Column#1)->Projection", "Hints": "USE_INDEX(@`sel_1` `test`.`t1` ), USE_INDEX(@`sel_2` `test`.`t2` `f`), USE_INDEX(@`sel_2` `test`.`t3` `c_d_e`), HASH_JOIN(@`sel_2` `test`.`t2`), INL_JOIN(@`sel_1` `test`.`t1`)" }, { "SQL": "select /*+ INL_JOIN(@sel_1 t1), HASH_JOIN(@qb t2) */ t1.a, t1.b from t t1, (select /*+ QB_NAME(qb) */ t2.a from t t2, t t3 where t2.a = t3.c) s where t1.a=s.a", - "Plan": "IndexMergeJoin{TableReader(Table(t))->LeftHashJoin{IndexReader(Index(t.f)[[NULL,+inf]])->IndexReader(Index(t.c_d_e)[[NULL,+inf]])}(Column#13,Column#27)}(Column#13,Column#1)->Projection", + "Plan": "IndexJoin{TableReader(Table(t))->LeftHashJoin{IndexReader(Index(t.f)[[NULL,+inf]])->IndexReader(Index(t.c_d_e)[[NULL,+inf]])}(Column#13,Column#27)}(Column#13,Column#1)->Projection", "Hints": "USE_INDEX(@`sel_1` `test`.`t1` ), USE_INDEX(@`sel_2` `test`.`t2` `f`), USE_INDEX(@`sel_2` `test`.`t3` `c_d_e`), HASH_JOIN(@`sel_2` `test`.`t2`), INL_JOIN(@`sel_1` `test`.`t1`)" }, { @@ -1280,5 +1280,22 @@ "Best": "Apply{IndexReader(Index(t.f)[[NULL,+inf]])->IndexMergeJoin{IndexReader(Index(t.c_d_e)[[NULL,+inf]]->HashAgg)->HashAgg->IndexReader(Index(t.g)[[NULL,+inf]])}(Column#29,Column#23)}->HashAgg" } ] + }, + { + "Name": "TestIndexJoinHint", + "Cases": [ + { + "SQL": "select /*+ INL_JOIN(t1) */ * from t1 join t2 on t1.a = t2.a;", + "Plan": "IndexJoin{IndexLookUp(Index(t1.idx_a)[[NULL,+inf]]->Sel([not(isnull(Column#1))]), Table(t1))->TableReader(Table(t2)->Sel([not(isnull(Column#4))]))}(Column#4,Column#1)" + }, + { + "SQL": "select /*+ INL_HASH_JOIN(t1) */ * from t1 join t2 on t1.a = t2.a;", + "Plan": "IndexHashJoin{IndexLookUp(Index(t1.idx_a)[[NULL,+inf]]->Sel([not(isnull(Column#1))]), Table(t1))->TableReader(Table(t2)->Sel([not(isnull(Column#4))]))}(Column#4,Column#1)" + }, + { + "SQL": "select /*+ INL_MERGE_JOIN(t1) */ * from t1 join t2 on t1.a = t2.a;", + "Plan": "IndexMergeJoin{IndexLookUp(Index(t1.idx_a)[[NULL,+inf]]->Sel([not(isnull(Column#1))]), Table(t1))->Projection->TableReader(Table(t2)->Sel([not(isnull(Column#4))]))}(Column#4,Column#1)" + } + ] } ]