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: chose outer table based on cost when both tables are specified in TIDB_INLJ (#9579) #9615

Merged
merged 3 commits into from
Mar 11, 2019
Merged
Show file tree
Hide file tree
Changes from all 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
22 changes: 22 additions & 0 deletions cmd/explaintest/r/index_join.result
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
drop table if exists t1, t2;
create table t1(a bigint, b bigint, index idx(a));
create table t2(a bigint, b bigint, index idx(a));
insert into t1 values(1, 1), (1, 1), (1, 1), (1, 1), (1, 1);
insert into t2 values(1, 1);
analyze table t1, t2;
explain select /*+ TIDB_INLJ(t1, t2) */ * from t1 join t2 on t1.a=t2.a;
id count task operator info
IndexJoin_14 1.25 root inner join, inner:IndexLookUp_13, outer key:test.t2.a, inner key:test.t1.a
├─IndexLookUp_13 5.00 root
│ ├─IndexScan_11 5.00 cop table:t1, index:a, range: decided by [test.t2.a], keep order:false
│ └─TableScan_12 5.00 cop table:t1, keep order:false
└─TableReader_16 1.00 root data:TableScan_15
└─TableScan_15 1.00 cop table:t2, range:[-inf,+inf], keep order:false
explain select * from t1 join t2 on t1.a=t2.a;
id count task operator info
IndexJoin_14 1.25 root inner join, inner:IndexLookUp_13, outer key:test.t2.a, inner key:test.t1.a
├─IndexLookUp_13 5.00 root
│ ├─IndexScan_11 5.00 cop table:t1, index:a, range: decided by [test.t2.a], keep order:false
│ └─TableScan_12 5.00 cop table:t1, keep order:false
└─TableReader_26 1.00 root data:TableScan_25
└─TableScan_25 1.00 cop table:t2, range:[-inf,+inf], keep order:false
12 changes: 12 additions & 0 deletions cmd/explaintest/t/index_join.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
drop table if exists t1, t2;
create table t1(a bigint, b bigint, index idx(a));
create table t2(a bigint, b bigint, index idx(a));
insert into t1 values(1, 1), (1, 1), (1, 1), (1, 1), (1, 1);
insert into t2 values(1, 1);

analyze table t1, t2;

-- Test https://github.com/pingcap/tidb/issues/9577
-- we expect the following two SQL chose t2 as the outer table
explain select /*+ TIDB_INLJ(t1, t2) */ * from t1 join t2 on t1.a=t2.a;
explain select * from t1 join t2 on t1.a=t2.a;
19 changes: 19 additions & 0 deletions executor/index_lookup_join_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,22 @@ func (s *testSuite) TestIndexLookupJoinHang(c *C) {
}
rs.Close()
}

func (s *testSuite) TestInapplicableIndexJoinHint(c *C) {
tk := testkit.NewTestKitWithInit(c, s.store)
tk.MustExec(`drop table if exists t1, t2;`)
tk.MustExec(`create table t1(a bigint, b bigint);`)
tk.MustExec(`create table t2(a bigint, b bigint);`)
tk.MustQuery(`select /*+ TIDB_INLJ(t1, t2) */ * from t1, t2;`).Check(testkit.Rows())
tk.MustQuery(`show warnings;`).Check(testkit.Rows(`Warning 1815 Optimizer Hint /*+ TIDB_INLJ(t1, t2) */ is inapplicable without column equal ON condition`))
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 /*+ TIDB_INLJ(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);`)
tk.MustQuery(`select /*+ TIDB_INLJ(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 /*+ 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 /*+ TIDB_INLJ(t2) */ is inapplicable`))
}
59 changes: 33 additions & 26 deletions planner/core/exhaust_physical_plans.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
package core

import (
"fmt"
"math"

"github.com/pingcap/parser/ast"
Expand Down Expand Up @@ -614,62 +615,68 @@ func (p *LogicalJoin) buildFakeEqCondsForIndexJoin(keys, idxCols []*expression.C

// 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) ([]PhysicalPlan, bool) {
plans := make([]PhysicalPlan, 0, 2)
func (p *LogicalJoin) tryToGetIndexJoin(prop *property.PhysicalProperty) (indexJoins []PhysicalPlan, forced bool) {
rightOuter := (p.preferJoinType & preferLeftAsIndexInner) > 0
leftOuter := (p.preferJoinType & preferRightAsIndexInner) > 0
if len(p.EqualConditions) == 0 {
if leftOuter || rightOuter {
warning := ErrInternal.GenWithStack("TIDB_INLJ hint is inapplicable without column equal ON condition")
hasIndexJoinHint := leftOuter || rightOuter

defer func() {
if !forced && hasIndexJoinHint {
// Construct warning message prefix.
errMsg := "Optimizer Hint TIDB_INLJ is inapplicable"
if p.hintInfo != nil {
errMsg = fmt.Sprintf("Optimizer Hint %s is inapplicable", p.hintInfo.restore2IndexJoinHint())
}

// Append inapplicable reason.
if len(p.EqualConditions) == 0 {
errMsg += " without column equal ON condition"
}

// Generate warning message to client.
warning := ErrInternal.GenWithStack(errMsg)
p.ctx.GetSessionVars().StmtCtx.AppendWarning(warning)
}
}()

if len(p.EqualConditions) == 0 {
return nil, false
}

switch p.JoinType {
case SemiJoin, AntiSemiJoin, LeftOuterSemiJoin, AntiLeftOuterSemiJoin, LeftOuterJoin:
join := p.getIndexJoinByOuterIdx(prop, 0)
if join != nil {
// If the plan is not nil and matches the hint, return it directly.
if leftOuter {
return join, true
}
plans = append(plans, join...)
}
return join, join != nil && leftOuter
case RightOuterJoin:
join := p.getIndexJoinByOuterIdx(prop, 1)
if join != nil {
// If the plan is not nil and matches the hint, return it directly.
if rightOuter {
return join, true
}
plans = append(plans, join...)
}
return join, join != nil && rightOuter
case InnerJoin:
lhsCardinality := p.Children()[0].statsInfo().Count()
rhsCardinality := p.Children()[1].statsInfo().Count()

leftJoins := p.getIndexJoinByOuterIdx(prop, 0)
if leftOuter && leftJoins != nil {
if leftJoins != nil && leftOuter && !rightOuter {
return leftJoins, true
}

rightJoins := p.getIndexJoinByOuterIdx(prop, 1)
if rightOuter && rightJoins != nil {
if rightJoins != nil && rightOuter && !leftOuter {
return rightJoins, true
}

if leftJoins != nil && lhsCardinality < rhsCardinality {
return leftJoins, leftOuter
return leftJoins, hasIndexJoinHint
}

if rightJoins != nil && rhsCardinality < lhsCardinality {
return rightJoins, rightOuter
return rightJoins, hasIndexJoinHint
}

plans = append(plans, leftJoins...)
plans = append(plans, rightJoins...)
joins := append(leftJoins, rightJoins...)
return joins, hasIndexJoinHint && len(joins) != 0
}
return plans, false

return nil, false
}

// LogicalJoin can generates hash join, index join and sort merge join.
Expand Down
5 changes: 5 additions & 0 deletions planner/core/logical_plan_builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,11 @@ func (p *LogicalJoin) setPreferredJoinType(hintInfo *tableHintInfo) error {
p.preferJoinType |= preferRightAsIndexInner
}

// set hintInfo for further usage if this hint info can be used.
if p.preferJoinType != 0 {
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 {
Expand Down
11 changes: 7 additions & 4 deletions planner/core/logical_plans.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,10 +95,13 @@ const (
type LogicalJoin struct {
logicalSchemaProducer

JoinType JoinType
reordered bool
cartesianJoin bool
StraightJoin bool
JoinType JoinType
reordered bool
cartesianJoin bool
StraightJoin bool

// hintInfo stores the join algorithm hint information specified by client.
hintInfo *tableHintInfo
preferJoinType uint

EqualConditions []*expression.ScalarFunction
Expand Down
4 changes: 1 addition & 3 deletions planner/core/physical_plan_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ import (
"github.com/pingcap/tidb/planner/core"
"github.com/pingcap/tidb/session"
"github.com/pingcap/tidb/sessionctx"
"github.com/pingcap/tidb/terror"
"github.com/pingcap/tidb/util/testleak"
"golang.org/x/net/context"
)
Expand Down Expand Up @@ -1302,6 +1301,5 @@ func (s *testPlanSuite) TestIndexLookupCartesianJoin(c *C) {
c.Assert(core.ToString(p), Equals, "LeftHashJoin{TableReader(Table(t))->TableReader(Table(t))}")
warnings := se.GetSessionVars().StmtCtx.GetWarnings()
lastWarn := warnings[len(warnings)-1]
err = core.ErrInternal.GenWithStack("TIDB_INLJ hint is inapplicable without column equal ON condition")
c.Assert(terror.ErrorEqual(err, lastWarn.Err), IsTrue)
c.Assert(lastWarn.Err.Error(), Equals, "[planner:1815]Optimizer Hint /*+ TIDB_INLJ(t1, t2) */ is inapplicable without column equal ON condition")
}
13 changes: 13 additions & 0 deletions planner/core/planbuilder.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
package core

import (
"bytes"
"fmt"
"strings"

Expand Down Expand Up @@ -83,6 +84,18 @@ func (info *tableHintInfo) matchTableName(tables []*model.CIStr, tablesInHints [
return false
}

func (info *tableHintInfo) restore2IndexJoinHint() string {
buffer := bytes.NewBufferString("/*+ TIDB_INLJ(")
for i, tableName := range info.indexNestedLoopJoinTables {
buffer.WriteString(tableName.O)
if i < len(info.indexNestedLoopJoinTables)-1 {
buffer.WriteString(", ")
}
}
buffer.WriteString(") */")
return buffer.String()
}

// clauseCode indicates in which clause the column is currently.
type clauseCode int

Expand Down