diff --git a/planner/core/casetest/rule/BUILD.bazel b/planner/core/casetest/rule/BUILD.bazel index 7949277ddcb3b..c0fc070d0951a 100644 --- a/planner/core/casetest/rule/BUILD.bazel +++ b/planner/core/casetest/rule/BUILD.bazel @@ -12,7 +12,7 @@ go_test( ], data = glob(["testdata/**"]), flaky = True, - shard_count = 23, + shard_count = 24, deps = [ "//domain", "//expression", diff --git a/planner/core/casetest/rule/rule_join_reorder_test.go b/planner/core/casetest/rule/rule_join_reorder_test.go index 5701fcae99279..07138b9220230 100644 --- a/planner/core/casetest/rule/rule_join_reorder_test.go +++ b/planner/core/casetest/rule/rule_join_reorder_test.go @@ -72,6 +72,19 @@ func TestNoHashJoinHint(t *testing.T) { runJoinReorderTestData(t, tk, "TestNoHashJoinHint") } +// test the global/session variable tidb_opt_enable_hash_join being set to no +func TestOptEnableHashJoin(t *testing.T) { + store := testkit.CreateMockStore(t) + tk := testkit.NewTestKit(t, store) + tk.MustExec("use test") + tk.MustExec("set tidb_opt_enable_hash_join=off") + tk.MustExec("create table t1(a int, b int, key(a));") + tk.MustExec("create table t2(a int, b int, key(a));") + tk.MustExec("create table t3(a int, b int, key(a));") + tk.MustExec("create table t4(a int, b int, key(a));") + runJoinReorderTestData(t, tk, "TestOptEnableHashJoin") +} + func TestNoMergeJoinHint(t *testing.T) { store := testkit.CreateMockStore(t) tk := testkit.NewTestKit(t, store) diff --git a/planner/core/casetest/rule/testdata/join_reorder_suite_in.json b/planner/core/casetest/rule/testdata/join_reorder_suite_in.json index 1201331e17a43..c3c3ece36dba5 100644 --- a/planner/core/casetest/rule/testdata/join_reorder_suite_in.json +++ b/planner/core/casetest/rule/testdata/join_reorder_suite_in.json @@ -49,6 +49,21 @@ "select /*+ leading(t1, t2, t3, t4), hash_join(t1, t2), no_hash_join(t3), hash_join(t4) */ * from t1, t2, t3, t4" ] }, + { + "name": "TestOptEnableHashJoin", + "cases": [ + "select * from t1, t2", + "select * from t1, t2 where t1.a=t2.a", + "select * from t1, t2 where t1.b=t2.b", + "select * from t1, t2 where t1.a=t2.a and t1.b=t2.b", + "select * from t1 left join t2 on t1.b=t2.b", + "select * from t1 left join t2 on t1.a=t2.a", + "select * from t1 right join t2 on t1.b=t2.b", + "select * from t1 right join t2 on t1.a=t2.a", + "select /*+ hash_join(t1) */ * from t1, t2", + "select /*+ hash_join(t2) */ * from t1, t2" + ] + }, { "name": "TestNoIndexJoinHint", "cases": [ diff --git a/planner/core/casetest/rule/testdata/join_reorder_suite_out.json b/planner/core/casetest/rule/testdata/join_reorder_suite_out.json index fd4c581d478ee..9a5eb9881759f 100644 --- a/planner/core/casetest/rule/testdata/join_reorder_suite_out.json +++ b/planner/core/casetest/rule/testdata/join_reorder_suite_out.json @@ -609,7 +609,7 @@ " └─TableFullScan 10000.00 cop[tikv] table:t1 keep order:false, stats:pseudo" ], "Warning": [ - "Warning 1815 Some HASH_JOIN and NO_HASH_JOIN hints conflict, NO_HASH_JOIN is ignored" + "Warning 1815 A conflict between the HASH_JOIN hint and the NO_HASH_JOIN hint, or the tidb_opt_enable_hash_join system variable, the HASH_JOIN hint will take precedence." ] }, { @@ -622,7 +622,7 @@ " └─TableFullScan 10000.00 cop[tikv] table:t1 keep order:false, stats:pseudo" ], "Warning": [ - "Warning 1815 Some HASH_JOIN and NO_HASH_JOIN hints conflict, NO_HASH_JOIN is ignored" + "Warning 1815 A conflict between the HASH_JOIN hint and the NO_HASH_JOIN hint, or the tidb_opt_enable_hash_join system variable, the HASH_JOIN hint will take precedence." ] }, { @@ -782,6 +782,147 @@ } ] }, + { + "Name": "TestOptEnableHashJoin", + "Cases": [ + { + "SQL": "select * from t1, t2", + "Plan": [ + "MergeJoin 100000000.00 root inner join", + "├─TableReader(Build) 10000.00 root data:TableFullScan", + "│ └─TableFullScan 10000.00 cop[tikv] table:t2 keep order:false, stats:pseudo", + "└─TableReader(Probe) 10000.00 root data:TableFullScan", + " └─TableFullScan 10000.00 cop[tikv] table:t1 keep order:false, stats:pseudo" + ], + "Warning": null + }, + { + "SQL": "select * from t1, t2 where t1.a=t2.a", + "Plan": [ + "IndexHashJoin 12487.50 root inner join, inner:IndexLookUp, outer key:test.t1.a, inner key:test.t2.a, equal cond:eq(test.t1.a, test.t2.a)", + "├─TableReader(Build) 9990.00 root data:Selection", + "│ └─Selection 9990.00 cop[tikv] not(isnull(test.t1.a))", + "│ └─TableFullScan 10000.00 cop[tikv] table:t1 keep order:false, stats:pseudo", + "└─IndexLookUp(Probe) 12487.50 root ", + " ├─Selection(Build) 12487.50 cop[tikv] not(isnull(test.t2.a))", + " │ └─IndexRangeScan 12500.00 cop[tikv] table:t2, index:a(a) range: decided by [eq(test.t2.a, test.t1.a)], keep order:false, stats:pseudo", + " └─TableRowIDScan(Probe) 12487.50 cop[tikv] table:t2 keep order:false, stats:pseudo" + ], + "Warning": null + }, + { + "SQL": "select * from t1, t2 where t1.b=t2.b", + "Plan": [ + "MergeJoin 12487.50 root inner join, left key:test.t1.b, right key:test.t2.b", + "├─Sort(Build) 9990.00 root test.t2.b", + "│ └─TableReader 9990.00 root data:Selection", + "│ └─Selection 9990.00 cop[tikv] not(isnull(test.t2.b))", + "│ └─TableFullScan 10000.00 cop[tikv] table:t2 keep order:false, stats:pseudo", + "└─Sort(Probe) 9990.00 root test.t1.b", + " └─TableReader 9990.00 root data:Selection", + " └─Selection 9990.00 cop[tikv] not(isnull(test.t1.b))", + " └─TableFullScan 10000.00 cop[tikv] table:t1 keep order:false, stats:pseudo" + ], + "Warning": null + }, + { + "SQL": "select * from t1, t2 where t1.a=t2.a and t1.b=t2.b", + "Plan": [ + "IndexHashJoin 12475.01 root inner join, inner:IndexLookUp, outer key:test.t1.a, inner key:test.t2.a, equal cond:eq(test.t1.a, test.t2.a), eq(test.t1.b, test.t2.b)", + "├─TableReader(Build) 9980.01 root data:Selection", + "│ └─Selection 9980.01 cop[tikv] not(isnull(test.t1.a)), not(isnull(test.t1.b))", + "│ └─TableFullScan 10000.00 cop[tikv] table:t1 keep order:false, stats:pseudo", + "└─IndexLookUp(Probe) 12475.01 root ", + " ├─Selection(Build) 12487.50 cop[tikv] not(isnull(test.t2.a))", + " │ └─IndexRangeScan 12500.00 cop[tikv] table:t2, index:a(a) range: decided by [eq(test.t2.a, test.t1.a)], keep order:false, stats:pseudo", + " └─Selection(Probe) 12475.01 cop[tikv] not(isnull(test.t2.b))", + " └─TableRowIDScan 12487.50 cop[tikv] table:t2 keep order:false, stats:pseudo" + ], + "Warning": null + }, + { + "SQL": "select * from t1 left join t2 on t1.b=t2.b", + "Plan": [ + "MergeJoin 12487.50 root left outer join, left key:test.t1.b, right key:test.t2.b", + "├─Sort(Build) 9990.00 root test.t2.b", + "│ └─TableReader 9990.00 root data:Selection", + "│ └─Selection 9990.00 cop[tikv] not(isnull(test.t2.b))", + "│ └─TableFullScan 10000.00 cop[tikv] table:t2 keep order:false, stats:pseudo", + "└─Sort(Probe) 10000.00 root test.t1.b", + " └─TableReader 10000.00 root data:TableFullScan", + " └─TableFullScan 10000.00 cop[tikv] table:t1 keep order:false, stats:pseudo" + ], + "Warning": null + }, + { + "SQL": "select * from t1 left join t2 on t1.a=t2.a", + "Plan": [ + "IndexHashJoin 12487.50 root left outer join, inner:IndexLookUp, outer key:test.t1.a, inner key:test.t2.a, equal cond:eq(test.t1.a, test.t2.a)", + "├─TableReader(Build) 10000.00 root data:TableFullScan", + "│ └─TableFullScan 10000.00 cop[tikv] table:t1 keep order:false, stats:pseudo", + "└─IndexLookUp(Probe) 12487.50 root ", + " ├─Selection(Build) 12487.50 cop[tikv] not(isnull(test.t2.a))", + " │ └─IndexRangeScan 12500.00 cop[tikv] table:t2, index:a(a) range: decided by [eq(test.t2.a, test.t1.a)], keep order:false, stats:pseudo", + " └─TableRowIDScan(Probe) 12487.50 cop[tikv] table:t2 keep order:false, stats:pseudo" + ], + "Warning": null + }, + { + "SQL": "select * from t1 right join t2 on t1.b=t2.b", + "Plan": [ + "MergeJoin 12487.50 root right outer join, left key:test.t1.b, right key:test.t2.b", + "├─Sort(Build) 9990.00 root test.t1.b", + "│ └─TableReader 9990.00 root data:Selection", + "│ └─Selection 9990.00 cop[tikv] not(isnull(test.t1.b))", + "│ └─TableFullScan 10000.00 cop[tikv] table:t1 keep order:false, stats:pseudo", + "└─Sort(Probe) 10000.00 root test.t2.b", + " └─TableReader 10000.00 root data:TableFullScan", + " └─TableFullScan 10000.00 cop[tikv] table:t2 keep order:false, stats:pseudo" + ], + "Warning": null + }, + { + "SQL": "select * from t1 right join t2 on t1.a=t2.a", + "Plan": [ + "IndexHashJoin 12487.50 root right outer join, inner:IndexLookUp, outer key:test.t2.a, inner key:test.t1.a, equal cond:eq(test.t2.a, test.t1.a)", + "├─TableReader(Build) 10000.00 root data:TableFullScan", + "│ └─TableFullScan 10000.00 cop[tikv] table:t2 keep order:false, stats:pseudo", + "└─IndexLookUp(Probe) 12487.50 root ", + " ├─Selection(Build) 12487.50 cop[tikv] not(isnull(test.t1.a))", + " │ └─IndexRangeScan 12500.00 cop[tikv] table:t1, index:a(a) range: decided by [eq(test.t1.a, test.t2.a)], keep order:false, stats:pseudo", + " └─TableRowIDScan(Probe) 12487.50 cop[tikv] table:t1 keep order:false, stats:pseudo" + ], + "Warning": null + }, + { + "SQL": "select /*+ hash_join(t1) */ * from t1, t2", + "Plan": [ + "HashJoin 100000000.00 root CARTESIAN inner join", + "├─TableReader(Build) 10000.00 root data:TableFullScan", + "│ └─TableFullScan 10000.00 cop[tikv] table:t2 keep order:false, stats:pseudo", + "└─TableReader(Probe) 10000.00 root data:TableFullScan", + " └─TableFullScan 10000.00 cop[tikv] table:t1 keep order:false, stats:pseudo" + ], + "Warning": [ + "Warning 1815 A conflict between the HASH_JOIN hint and the NO_HASH_JOIN hint, or the tidb_opt_enable_hash_join system variable, the HASH_JOIN hint will take precedence." + ] + }, + { + "SQL": "select /*+ hash_join(t2) */ * from t1, t2", + "Plan": [ + "HashJoin 100000000.00 root CARTESIAN inner join", + "├─TableReader(Build) 10000.00 root data:TableFullScan", + "│ └─TableFullScan 10000.00 cop[tikv] table:t2 keep order:false, stats:pseudo", + "└─TableReader(Probe) 10000.00 root data:TableFullScan", + " └─TableFullScan 10000.00 cop[tikv] table:t1 keep order:false, stats:pseudo" + ], + "Warning": [ + "Warning 1815 A conflict between the HASH_JOIN hint and the NO_HASH_JOIN hint, or the tidb_opt_enable_hash_join system variable, the HASH_JOIN hint will take precedence." + ] + } + ] + }, + { "Name": "TestNoIndexJoinHint", "Cases": [ diff --git a/planner/core/exhaust_physical_plans.go b/planner/core/exhaust_physical_plans.go index e24f9f291b113..1098844ac05fe 100644 --- a/planner/core/exhaust_physical_plans.go +++ b/planner/core/exhaust_physical_plans.go @@ -244,7 +244,7 @@ func (p *LogicalJoin) GetMergeJoin(prop *property.PhysicalProperty, schema *expr // If TiDB_SMJ hint is existed, it should consider enforce merge join, // because we can't trust lhsChildProperty completely. if (p.preferJoinType&preferMergeJoin) > 0 || - (p.preferJoinType&preferNoHashJoin) > 0 { // if hash join is not allowed, generate as many other types of join as possible to avoid 'cant-find-plan' error. + p.shouldSkipHashJoin() { // if hash join is not allowed, generate as many other types of join as possible to avoid 'cant-find-plan' error. joins = append(joins, p.getEnforcedMergeJoin(prop, schema, statsInfo)...) } @@ -391,6 +391,10 @@ var ForceUseOuterBuild4Test = atomic.NewBool(false) // TODO: use hint and remove this variable var ForcedHashLeftJoin4Test = atomic.NewBool(false) +func (p *LogicalJoin) shouldSkipHashJoin() bool { + return (p.preferJoinType&preferNoHashJoin) > 0 || (p.SCtx().GetSessionVars().DisableHashJoin) +} + func (p *LogicalJoin) getHashJoins(prop *property.PhysicalProperty) (joins []PhysicalPlan, forced bool) { if !prop.IsSortItemEmpty() { // hash join doesn't promise any orders return @@ -451,12 +455,12 @@ func (p *LogicalJoin) getHashJoins(prop *property.PhysicalProperty) (joins []Phy } forced = (p.preferJoinType&preferHashJoin > 0) || forceLeftToBuild || forceRightToBuild - noHashJoin := (p.preferJoinType & preferNoHashJoin) > 0 - if !forced && noHashJoin { + if !forced && p.shouldSkipHashJoin() { return nil, false - } else if forced && noHashJoin { + } else if forced && p.shouldSkipHashJoin() { p.SCtx().GetSessionVars().StmtCtx.AppendWarning(ErrInternal.GenWithStack( - "Some HASH_JOIN and NO_HASH_JOIN hints conflict, NO_HASH_JOIN is ignored")) + "A conflict between the HASH_JOIN hint and the NO_HASH_JOIN hint, " + + "or the tidb_opt_enable_hash_join system variable, the HASH_JOIN hint will take precedence.")) } return } diff --git a/sessionctx/variable/session.go b/sessionctx/variable/session.go index 591271e1a6ce3..398f7dc52c6f2 100644 --- a/sessionctx/variable/session.go +++ b/sessionctx/variable/session.go @@ -1244,6 +1244,9 @@ type SessionVars struct { // AnalyzeVersion indicates how TiDB collect and use analyzed statistics. AnalyzeVersion int + // DisableHashJoin indicates whether to disable hash join. + DisableHashJoin bool + // EnableHistoricalStats indicates whether to enable historical statistics. EnableHistoricalStats bool diff --git a/sessionctx/variable/sysvar.go b/sessionctx/variable/sysvar.go index 1c08d94a00f25..f8870466509a2 100644 --- a/sessionctx/variable/sysvar.go +++ b/sessionctx/variable/sysvar.go @@ -2056,6 +2056,10 @@ var defaultSysVars = []*SysVar{ s.AnalyzeVersion = tidbOptPositiveInt32(val, DefTiDBAnalyzeVersion) return nil }}, + {Scope: ScopeGlobal | ScopeSession, Name: TiDBOptEnableHashJoin, Value: BoolToOnOff(DefTiDBOptEnableHashJoin), Type: TypeBool, SetSession: func(s *SessionVars, val string) error { + s.DisableHashJoin = !TiDBOptOn(val) + return nil + }}, {Scope: ScopeGlobal | ScopeSession, Name: TiDBEnableIndexMergeJoin, Value: BoolToOnOff(DefTiDBEnableIndexMergeJoin), Hidden: true, Type: TypeBool, SetSession: func(s *SessionVars, val string) error { s.EnableIndexMergeJoin = TiDBOptOn(val) return nil diff --git a/sessionctx/variable/tidb_vars.go b/sessionctx/variable/tidb_vars.go index 1be1d5e9e5a42..060f7b7a0f134 100644 --- a/sessionctx/variable/tidb_vars.go +++ b/sessionctx/variable/tidb_vars.go @@ -906,6 +906,9 @@ const ( // TiDBEnableCheckConstraint indicates whether to enable check constraint feature. TiDBEnableCheckConstraint = "tidb_enable_check_constraint" + // TiDBOptEnableHashJoin indicates whether to enable hash join. + TiDBOptEnableHashJoin = "tidb_opt_enable_hash_join" + // TiDBOptObjective indicates whether the optimizer should be more stable, predictable or more aggressive. // Please see comments of SessionVars.OptObjective for details. TiDBOptObjective = "tidb_opt_objective" @@ -1406,6 +1409,7 @@ const ( DefTiDBLockUnchangedKeys = true DefTiDBEnableCheckConstraint = false DefTiDBSkipMissingPartitionStats = true + DefTiDBOptEnableHashJoin = true DefTiDBOptObjective = OptObjectiveModerate DefTiDBSchemaVersionCacheLimit = 16 )