Skip to content

Commit 71df67c

Browse files
committed
[memo] variable to disable merge join
1 parent 6ad8521 commit 71df67c

File tree

8 files changed

+107
-20
lines changed

8 files changed

+107
-20
lines changed

enginetest/join_planning_tests.go

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,11 +43,13 @@ type JoinPlanTest struct {
4343
skipOld bool
4444
}
4545

46-
var JoinPlanningTests = []struct {
46+
type joinPlanScript struct {
4747
name string
4848
setup []string
4949
tests []JoinPlanTest
50-
}{
50+
}
51+
52+
var JoinPlanningTests = []joinPlanScript{
5153
{
5254
name: "filter pushdown through join uppercase name",
5355
setup: []string{
@@ -78,6 +80,33 @@ var JoinPlanningTests = []struct {
7880
},
7981
},
8082
},
83+
{
84+
name: "block merge join",
85+
setup: []string{
86+
"CREATE table xy (x int primary key, y int, unique index y_idx(y));",
87+
"CREATE table ab (a int primary key, b int);",
88+
"insert into xy values (1,0), (2,1), (0,2), (3,3);",
89+
"insert into ab values (0,2), (1,2), (2,2), (3,1);",
90+
`analyze table xy update histogram on x using data '{"row_count":1000}'`,
91+
`analyze table ab update histogram on a using data '{"row_count":1000}'`,
92+
},
93+
tests: []JoinPlanTest{
94+
{
95+
q: "select /*+ JOIN_ORDER(ab, xy) MERGE_JOIN(ab, xy)*/ * from ab join xy on y = a order by 1, 3",
96+
types: []plan.JoinType{plan.JoinTypeMerge},
97+
exp: []sql.Row{{0, 2, 1, 0}, {1, 2, 2, 1}, {2, 2, 0, 2}, {3, 1, 3, 3}},
98+
},
99+
{
100+
q: "set @@SESSION.disable_merge_join = 1",
101+
exp: []sql.Row{{}},
102+
},
103+
{
104+
q: "select /*+ JOIN_ORDER(ab, xy) MERGE_JOIN(ab, xy)*/ * from ab join xy on y = a order by 1, 3",
105+
types: []plan.JoinType{plan.JoinTypeLookup},
106+
exp: []sql.Row{{0, 2, 1, 0}, {1, 2, 2, 1}, {2, 2, 0, 2}, {3, 1, 3, 3}},
107+
},
108+
},
109+
},
81110
{
82111
name: "merge join unary index",
83112
setup: []string{
@@ -1725,7 +1754,11 @@ join uv d on d.u = c.x`,
17251754
}
17261755

17271756
func TestJoinPlanning(t *testing.T, harness Harness) {
1728-
for _, tt := range JoinPlanningTests {
1757+
runJoinPlanningTests(t, harness, JoinPlanningTests)
1758+
}
1759+
1760+
func runJoinPlanningTests(t *testing.T, harness Harness, tests []joinPlanScript) {
1761+
for _, tt := range tests {
17291762
t.Run(tt.name, func(t *testing.T) {
17301763
harness.Setup([]setup.SetupScript{setup.MydbData[0], tt.setup})
17311764
e := mustNewEngine(t, harness)
@@ -1750,7 +1783,6 @@ func TestJoinPlanning(t *testing.T, harness Harness) {
17501783
})
17511784
}
17521785
}
1753-
17541786
func evalJoinTypeTest(t *testing.T, harness Harness, e QueryEngine, query string, types []plan.JoinType, skipOld bool) {
17551787
t.Run(query+" join types", func(t *testing.T) {
17561788
if skipOld {

sql/analyzer/indexed_joins.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,7 @@ func replanJoin(ctx *sql.Context, n *plan.JoinNode, a *Analyzer, scope *plan.Sco
200200
return nil, err
201201
}
202202

203+
m.SetDefaultHints()
203204
hints := memo.ExtractJoinHint(n)
204205
for _, h := range hints {
205206
// this should probably happen earlier, but the root is not

sql/memo/hinttype_string.go

Lines changed: 12 additions & 11 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

sql/memo/memo.go

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,12 @@ func (m *Memo) StatsProvider() sql.StatsProvider {
8282
return m.statsProv
8383
}
8484

85+
func (m *Memo) SetDefaultHints() {
86+
if val, _ := m.Ctx.GetSessionVariable(m.Ctx, sql.DisableMergeJoin); val.(int8) != 0 {
87+
m.ApplyHint(Hint{Typ: HintTypeNoMergeJoin})
88+
}
89+
}
90+
8591
// newExprGroup creates a new logical expression group to encapsulate the
8692
// action of a SQL clause.
8793
// TODO: this is supposed to deduplicate logically equivalent table scans
@@ -459,6 +465,11 @@ func (m *Memo) optimizeMemoGroup(grp *ExprGroup) error {
459465
// rather than a local property.
460466
func (m *Memo) updateBest(grp *ExprGroup, n RelExpr, cost float64) {
461467
if !m.hints.isEmpty() {
468+
for _, block := range m.hints.block {
469+
if !block.isOk(n) {
470+
return
471+
}
472+
}
462473
if m.hints.satisfiedBy(n) {
463474
if !grp.HintOk {
464475
grp.Best = n
@@ -514,17 +525,22 @@ func getProjectColset(p *Project) sql.ColSet {
514525
func (m *Memo) ApplyHint(hint Hint) {
515526
switch hint.Typ {
516527
case HintTypeJoinOrder:
517-
m.WithJoinOrder(hint.Args)
528+
m.SetJoinOrder(hint.Args)
518529
case HintTypeJoinFixedOrder:
530+
case HintTypeNoMergeJoin:
531+
m.SetBlockOp(plan.JoinTypeMerge)
532+
m.SetBlockOp(plan.JoinTypeSemiMerge)
533+
m.SetBlockOp(plan.JoinTypeAntiMerge)
534+
m.SetBlockOp(plan.JoinTypeLeftOuterMerge)
519535
case HintTypeInnerJoin, HintTypeMergeJoin, HintTypeLookupJoin, HintTypeHashJoin, HintTypeSemiJoin, HintTypeAntiJoin, HintTypeLeftOuterLookupJoin:
520-
m.WithJoinOp(hint.Typ, hint.Args[0], hint.Args[1])
536+
m.SetJoinOp(hint.Typ, hint.Args[0], hint.Args[1])
521537
case HintTypeLeftDeep:
522538
m.hints.leftDeep = true
523539
default:
524540
}
525541
}
526542

527-
func (m *Memo) WithJoinOrder(tables []string) {
543+
func (m *Memo) SetJoinOrder(tables []string) {
528544
// order maps groupId -> table dependencies
529545
order := make(map[sql.TableId]uint64)
530546
for i, t := range tables {
@@ -542,7 +558,11 @@ func (m *Memo) WithJoinOrder(tables []string) {
542558
}
543559
}
544560

545-
func (m *Memo) WithJoinOp(op HintType, left, right string) {
561+
func (m *Memo) SetBlockOp(op plan.JoinType) {
562+
m.hints.block = append(m.hints.block, joinBlockHint{op: op})
563+
}
564+
565+
func (m *Memo) SetJoinOp(op HintType, left, right string) {
546566
var lTab, rTab sql.TableId
547567
for _, n := range m.root.RelProps.TableIdNodes() {
548568
if strings.EqualFold(left, n.Name()) {

sql/memo/select_hints.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ const (
3232
HintTypeUnknown HintType = iota //
3333
HintTypeJoinOrder // JOIN_ORDER
3434
HintTypeJoinFixedOrder // JOIN_FIXED_ORDER
35+
HintTypeNoMergeJoin // NO_MERGE_JOIN
3536
HintTypeMergeJoin // MERGE_JOIN
3637
HintTypeLookupJoin // LOOKUP_JOIN
3738
HintTypeHashJoin // HASH_JOIN
@@ -81,6 +82,8 @@ func newHint(joinTyp string, args []string) Hint {
8182
typ = HintTypeNoIndexConditionPushDown
8283
case "left_deep":
8384
typ = HintTypeLeftDeep
85+
case "no_merge_join":
86+
typ = HintTypeNoMergeJoin
8487
default:
8588
typ = HintTypeUnknown
8689
}
@@ -111,6 +114,8 @@ func (h Hint) valid() bool {
111114
return len(h.Args) == 0
112115
case HintTypeLeftDeep:
113116
return len(h.Args) == 0
117+
case HintTypeNoMergeJoin:
118+
return true
114119
case HintTypeUnknown:
115120
return false
116121
default:
@@ -367,11 +372,29 @@ func (o joinOpHint) typeMatches(n RelExpr) bool {
367372
return true
368373
}
369374

375+
type joinBlockHint struct {
376+
op plan.JoinType
377+
}
378+
379+
func (o joinBlockHint) isOk(n RelExpr) bool {
380+
switch n := n.(type) {
381+
case JoinRel:
382+
jp := n.JoinPrivate()
383+
if !jp.Left.Best.Group().HintOk || !jp.Right.Best.Group().HintOk {
384+
// equiv closures can generate child plans that bypass hints
385+
return false
386+
}
387+
return !(jp.Op == o.op)
388+
}
389+
return true
390+
}
391+
370392
// joinHints wraps a collection of join hints. The memo
371393
// interfaces with this object during costing.
372394
type joinHints struct {
373395
ops []joinOpHint
374396
order *joinOrderHint
397+
block []joinBlockHint
375398
leftDeep bool
376399
}
377400

sql/memo/select_hints_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,7 @@ func TestOrderHintBuilding(t *testing.T) {
238238
t.Run(tt.name, func(t *testing.T) {
239239
j := NewJoinOrderBuilder(NewMemo(newContext(pro), nil, nil, 0, NewDefaultCoster(), nil))
240240
j.ReorderJoin(tt.plan)
241-
j.m.WithJoinOrder(tt.hint)
241+
j.m.SetJoinOrder(tt.hint)
242242
if tt.invalid {
243243
require.Equal(t, j.m.hints.order, (*joinOrderHint)(nil))
244244
} else {

sql/statistics.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ import (
2121
"time"
2222
)
2323

24+
const DisableMergeJoin = "disable_merge_join"
25+
2426
// StatisticsTable is a table that can provide information about its number of rows and other facts to improve query
2527
// planning performance.
2628
type StatisticsTable interface {

sql/variables/system_variables.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1049,6 +1049,14 @@ var systemVars = map[string]sql.SystemVariable{
10491049
Type: types.NewSystemBoolType("inmemory_joins"),
10501050
Default: int8(0),
10511051
},
1052+
"disable_merge_join": &sql.MysqlSystemVariable{
1053+
Name: sql.DisableMergeJoin,
1054+
Scope: sql.GetMysqlScope(sql.SystemVariableScope_Both),
1055+
Dynamic: true,
1056+
SetVarHintApplies: false,
1057+
Type: types.NewSystemBoolType(sql.DisableMergeJoin),
1058+
Default: int8(0),
1059+
},
10521060
"innodb_autoinc_lock_mode": &sql.MysqlSystemVariable{
10531061
Name: "innodb_autoinc_lock_mode",
10541062
Scope: sql.GetMysqlScope(sql.SystemVariableScope_Global),

0 commit comments

Comments
 (0)