Skip to content

Commit 552313d

Browse files
authored
[memo] variable to disable merge join (#2898)
* [memo] variable to disable merge join * cleanup * skip server engine test
1 parent 6ad8521 commit 552313d

File tree

9 files changed

+118
-21
lines changed

9 files changed

+118
-21
lines changed

enginetest/join_planning_tests.go

Lines changed: 41 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,8 +1754,17 @@ 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) {
1763+
if sh, ok := harness.(SkippingHarness); ok {
1764+
if sh.SkipQueryTest(tt.name) {
1765+
t.Skip(tt.name)
1766+
}
1767+
}
17301768
harness.Setup([]setup.SetupScript{setup.MydbData[0], tt.setup})
17311769
e := mustNewEngine(t, harness)
17321770
defer e.Close()
@@ -1750,7 +1788,6 @@ func TestJoinPlanning(t *testing.T, harness Harness) {
17501788
})
17511789
}
17521790
}
1753-
17541791
func evalJoinTypeTest(t *testing.T, harness Harness, e QueryEngine, query string, types []plan.JoinType, skipOld bool) {
17551792
t.Run(query+" join types", func(t *testing.T) {
17561793
if skipOld {

enginetest/memory_engine_test.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,11 @@ func TestLateralJoin(t *testing.T) {
111111

112112
// TestJoinPlanning runs join-specific tests for merge
113113
func TestJoinPlanning(t *testing.T) {
114-
enginetest.TestJoinPlanning(t, enginetest.NewDefaultMemoryHarness())
114+
harness := enginetest.NewDefaultMemoryHarness()
115+
if harness.IsUsingServer() {
116+
harness.QueriesToSkip("block merge join")
117+
}
118+
enginetest.TestJoinPlanning(t, harness)
115119
}
116120

117121
// TestJoinOps runs join-specific tests for merge

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: 34 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,32 @@ 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(func(n RelExpr) bool {
532+
switch n := n.(type) {
533+
case JoinRel:
534+
jp := n.JoinPrivate()
535+
if !jp.Left.Best.Group().HintOk || !jp.Right.Best.Group().HintOk {
536+
// equiv closures can generate child plans that bypass hints
537+
return false
538+
}
539+
if jp.Op.IsMerge() {
540+
return false
541+
}
542+
}
543+
return true
544+
})
519545
case HintTypeInnerJoin, HintTypeMergeJoin, HintTypeLookupJoin, HintTypeHashJoin, HintTypeSemiJoin, HintTypeAntiJoin, HintTypeLeftOuterLookupJoin:
520-
m.WithJoinOp(hint.Typ, hint.Args[0], hint.Args[1])
546+
m.SetJoinOp(hint.Typ, hint.Args[0], hint.Args[1])
521547
case HintTypeLeftDeep:
522548
m.hints.leftDeep = true
523549
default:
524550
}
525551
}
526552

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

545-
func (m *Memo) WithJoinOp(op HintType, left, right string) {
571+
func (m *Memo) SetBlockOp(cb func(n RelExpr) bool) {
572+
m.hints.block = append(m.hints.block, joinBlockHint{cb: cb})
573+
}
574+
575+
func (m *Memo) SetJoinOp(op HintType, left, right string) {
546576
var lTab, rTab sql.TableId
547577
for _, n := range m.root.RelProps.TableIdNodes() {
548578
if strings.EqualFold(left, n.Name()) {

sql/memo/select_hints.go

Lines changed: 14 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,20 @@ func (o joinOpHint) typeMatches(n RelExpr) bool {
367372
return true
368373
}
369374

375+
type joinBlockHint struct {
376+
cb func(n RelExpr) bool
377+
}
378+
379+
func (o joinBlockHint) isOk(n RelExpr) bool {
380+
return o.cb(n)
381+
}
382+
370383
// joinHints wraps a collection of join hints. The memo
371384
// interfaces with this object during costing.
372385
type joinHints struct {
373386
ops []joinOpHint
374387
order *joinOrderHint
388+
block []joinBlockHint
375389
leftDeep bool
376390
}
377391

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)