Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
40 changes: 36 additions & 4 deletions enginetest/join_planning_tests.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,13 @@ type JoinPlanTest struct {
skipOld bool
}

var JoinPlanningTests = []struct {
type joinPlanScript struct {
name string
setup []string
tests []JoinPlanTest
}{
}

var JoinPlanningTests = []joinPlanScript{
{
name: "filter pushdown through join uppercase name",
setup: []string{
Expand Down Expand Up @@ -78,6 +80,33 @@ var JoinPlanningTests = []struct {
},
},
},
{
name: "block merge join",
setup: []string{
"CREATE table xy (x int primary key, y int, unique index y_idx(y));",
"CREATE table ab (a int primary key, b int);",
"insert into xy values (1,0), (2,1), (0,2), (3,3);",
"insert into ab values (0,2), (1,2), (2,2), (3,1);",
`analyze table xy update histogram on x using data '{"row_count":1000}'`,
`analyze table ab update histogram on a using data '{"row_count":1000}'`,
},
tests: []JoinPlanTest{
{
q: "select /*+ JOIN_ORDER(ab, xy) MERGE_JOIN(ab, xy)*/ * from ab join xy on y = a order by 1, 3",
types: []plan.JoinType{plan.JoinTypeMerge},
exp: []sql.Row{{0, 2, 1, 0}, {1, 2, 2, 1}, {2, 2, 0, 2}, {3, 1, 3, 3}},
},
{
q: "set @@SESSION.disable_merge_join = 1",
exp: []sql.Row{{}},
},
{
q: "select /*+ JOIN_ORDER(ab, xy) MERGE_JOIN(ab, xy)*/ * from ab join xy on y = a order by 1, 3",
types: []plan.JoinType{plan.JoinTypeLookup},
exp: []sql.Row{{0, 2, 1, 0}, {1, 2, 2, 1}, {2, 2, 0, 2}, {3, 1, 3, 3}},
},
},
},
{
name: "merge join unary index",
setup: []string{
Expand Down Expand Up @@ -1725,7 +1754,11 @@ join uv d on d.u = c.x`,
}

func TestJoinPlanning(t *testing.T, harness Harness) {
for _, tt := range JoinPlanningTests {
runJoinPlanningTests(t, harness, JoinPlanningTests)
}

func runJoinPlanningTests(t *testing.T, harness Harness, tests []joinPlanScript) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
harness.Setup([]setup.SetupScript{setup.MydbData[0], tt.setup})
e := mustNewEngine(t, harness)
Expand All @@ -1750,7 +1783,6 @@ func TestJoinPlanning(t *testing.T, harness Harness) {
})
}
}

func evalJoinTypeTest(t *testing.T, harness Harness, e QueryEngine, query string, types []plan.JoinType, skipOld bool) {
t.Run(query+" join types", func(t *testing.T) {
if skipOld {
Expand Down
1 change: 1 addition & 0 deletions sql/analyzer/indexed_joins.go
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,7 @@ func replanJoin(ctx *sql.Context, n *plan.JoinNode, a *Analyzer, scope *plan.Sco
return nil, err
}

m.SetDefaultHints()
hints := memo.ExtractJoinHint(n)
for _, h := range hints {
// this should probably happen earlier, but the root is not
Expand Down
23 changes: 12 additions & 11 deletions sql/memo/hinttype_string.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

38 changes: 34 additions & 4 deletions sql/memo/memo.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,12 @@ func (m *Memo) StatsProvider() sql.StatsProvider {
return m.statsProv
}

func (m *Memo) SetDefaultHints() {
if val, _ := m.Ctx.GetSessionVariable(m.Ctx, sql.DisableMergeJoin); val.(int8) != 0 {
m.ApplyHint(Hint{Typ: HintTypeNoMergeJoin})
}
}

// newExprGroup creates a new logical expression group to encapsulate the
// action of a SQL clause.
// TODO: this is supposed to deduplicate logically equivalent table scans
Expand Down Expand Up @@ -459,6 +465,11 @@ func (m *Memo) optimizeMemoGroup(grp *ExprGroup) error {
// rather than a local property.
func (m *Memo) updateBest(grp *ExprGroup, n RelExpr, cost float64) {
if !m.hints.isEmpty() {
for _, block := range m.hints.block {
if !block.isOk(n) {
return
}
}
if m.hints.satisfiedBy(n) {
if !grp.HintOk {
grp.Best = n
Expand Down Expand Up @@ -514,17 +525,32 @@ func getProjectColset(p *Project) sql.ColSet {
func (m *Memo) ApplyHint(hint Hint) {
switch hint.Typ {
case HintTypeJoinOrder:
m.WithJoinOrder(hint.Args)
m.SetJoinOrder(hint.Args)
case HintTypeJoinFixedOrder:
case HintTypeNoMergeJoin:
m.SetBlockOp(func(n RelExpr) bool {
switch n := n.(type) {
case JoinRel:
jp := n.JoinPrivate()
if !jp.Left.Best.Group().HintOk || !jp.Right.Best.Group().HintOk {
// equiv closures can generate child plans that bypass hints
return false
}
if jp.Op.IsMerge() {
return false
}
}
return true
})
case HintTypeInnerJoin, HintTypeMergeJoin, HintTypeLookupJoin, HintTypeHashJoin, HintTypeSemiJoin, HintTypeAntiJoin, HintTypeLeftOuterLookupJoin:
m.WithJoinOp(hint.Typ, hint.Args[0], hint.Args[1])
m.SetJoinOp(hint.Typ, hint.Args[0], hint.Args[1])
case HintTypeLeftDeep:
m.hints.leftDeep = true
default:
}
}

func (m *Memo) WithJoinOrder(tables []string) {
func (m *Memo) SetJoinOrder(tables []string) {
// order maps groupId -> table dependencies
order := make(map[sql.TableId]uint64)
for i, t := range tables {
Expand All @@ -542,7 +568,11 @@ func (m *Memo) WithJoinOrder(tables []string) {
}
}

func (m *Memo) WithJoinOp(op HintType, left, right string) {
func (m *Memo) SetBlockOp(cb func(n RelExpr) bool) {
m.hints.block = append(m.hints.block, joinBlockHint{cb: cb})
}

func (m *Memo) SetJoinOp(op HintType, left, right string) {
var lTab, rTab sql.TableId
for _, n := range m.root.RelProps.TableIdNodes() {
if strings.EqualFold(left, n.Name()) {
Expand Down
14 changes: 14 additions & 0 deletions sql/memo/select_hints.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ const (
HintTypeUnknown HintType = iota //
HintTypeJoinOrder // JOIN_ORDER
HintTypeJoinFixedOrder // JOIN_FIXED_ORDER
HintTypeNoMergeJoin // NO_MERGE_JOIN
HintTypeMergeJoin // MERGE_JOIN
HintTypeLookupJoin // LOOKUP_JOIN
HintTypeHashJoin // HASH_JOIN
Expand Down Expand Up @@ -81,6 +82,8 @@ func newHint(joinTyp string, args []string) Hint {
typ = HintTypeNoIndexConditionPushDown
case "left_deep":
typ = HintTypeLeftDeep
case "no_merge_join":
typ = HintTypeNoMergeJoin
default:
typ = HintTypeUnknown
}
Expand Down Expand Up @@ -111,6 +114,8 @@ func (h Hint) valid() bool {
return len(h.Args) == 0
case HintTypeLeftDeep:
return len(h.Args) == 0
case HintTypeNoMergeJoin:
return true
case HintTypeUnknown:
return false
default:
Expand Down Expand Up @@ -367,11 +372,20 @@ func (o joinOpHint) typeMatches(n RelExpr) bool {
return true
}

type joinBlockHint struct {
cb func(n RelExpr) bool
}

func (o joinBlockHint) isOk(n RelExpr) bool {
return o.cb(n)
}

// joinHints wraps a collection of join hints. The memo
// interfaces with this object during costing.
type joinHints struct {
ops []joinOpHint
order *joinOrderHint
block []joinBlockHint
leftDeep bool
}

Expand Down
2 changes: 1 addition & 1 deletion sql/memo/select_hints_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,7 @@ func TestOrderHintBuilding(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
j := NewJoinOrderBuilder(NewMemo(newContext(pro), nil, nil, 0, NewDefaultCoster(), nil))
j.ReorderJoin(tt.plan)
j.m.WithJoinOrder(tt.hint)
j.m.SetJoinOrder(tt.hint)
if tt.invalid {
require.Equal(t, j.m.hints.order, (*joinOrderHint)(nil))
} else {
Expand Down
2 changes: 2 additions & 0 deletions sql/statistics.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ import (
"time"
)

const DisableMergeJoin = "disable_merge_join"

// StatisticsTable is a table that can provide information about its number of rows and other facts to improve query
// planning performance.
type StatisticsTable interface {
Expand Down
8 changes: 8 additions & 0 deletions sql/variables/system_variables.go
Original file line number Diff line number Diff line change
Expand Up @@ -1049,6 +1049,14 @@ var systemVars = map[string]sql.SystemVariable{
Type: types.NewSystemBoolType("inmemory_joins"),
Default: int8(0),
},
"disable_merge_join": &sql.MysqlSystemVariable{
Name: sql.DisableMergeJoin,
Scope: sql.GetMysqlScope(sql.SystemVariableScope_Both),
Dynamic: true,
SetVarHintApplies: false,
Type: types.NewSystemBoolType(sql.DisableMergeJoin),
Default: int8(0),
},
"innodb_autoinc_lock_mode": &sql.MysqlSystemVariable{
Name: "innodb_autoinc_lock_mode",
Scope: sql.GetMysqlScope(sql.SystemVariableScope_Global),
Expand Down
Loading