From 326568bb08af15797a896d4f3cef5efd13cc06ff Mon Sep 17 00:00:00 2001 From: Yuanjia Zhang Date: Mon, 3 Apr 2023 16:28:57 +0800 Subject: [PATCH] planner: non-prep plan cache supports 2-way joins (#42740) ref pingcap/tidb#36598 --- planner/core/plan_cache_param.go | 1 + planner/core/plan_cache_param_test.go | 2 +- planner/core/plan_cache_test.go | 44 +++++++++-- planner/core/plan_cacheable_checker.go | 87 +++++++++++++++------ planner/core/plan_cacheable_checker_test.go | 9 ++- 5 files changed, 112 insertions(+), 31 deletions(-) diff --git a/planner/core/plan_cache_param.go b/planner/core/plan_cache_param.go index c8c941099c..0e9586fc7d 100644 --- a/planner/core/plan_cache_param.go +++ b/planner/core/plan_cache_param.go @@ -43,6 +43,7 @@ var ( buf := new(strings.Builder) buf.Reset() restoreCtx := format.NewRestoreCtx(format.DefaultRestoreFlags, buf) + restoreCtx.Flags ^= format.RestoreKeyWordUppercase return restoreCtx }} ) diff --git a/planner/core/plan_cache_param_test.go b/planner/core/plan_cache_param_test.go index 79d832b28a..7641f741af 100644 --- a/planner/core/plan_cache_param_test.go +++ b/planner/core/plan_cache_param_test.go @@ -70,7 +70,7 @@ func TestParameterize(t *testing.T) { }, { "select a+1, sum(b) from t where a<10 group by a+1", - "SELECT `a`+1,SUM(`b`) FROM `t` WHERE `a` 1 and b < 2", // hint "select a, sum(b) as c from t1 where a > 1 and b < 2 group by a having sum(b) > 1", // having "select * from t1 limit 1", // limit - "select * from t1, t2", // join "select * from (select * from t1) t", // sub-query "insert into t1 values(1, 1)", // insert "insert into t1(a, b) select a, b from t1", // insert into select @@ -1656,7 +1652,6 @@ func TestNonPreparedPlanExplainWarning(t *testing.T) { "skip non-prepared plan-cache: queries that have hints, aggregation, window-function, order-by, limit and lock are not supported", "skip non-prepared plan-cache: queries that have hints, aggregation, window-function, order-by, limit and lock are not supported", "skip non-prepared plan-cache: queries that have hints, aggregation, window-function, order-by, limit and lock are not supported", - "skip non-prepared plan-cache: queries that access multiple tables are not supported", "skip non-prepared plan-cache: queries that have sub-queries are not supported", "skip non-prepared plan-cache: not a select statement", "skip non-prepared plan-cache: not a select statement", @@ -1771,6 +1766,45 @@ func TestNonPreparedPlanCachePanic(t *testing.T) { } } +func TestNonPreparedPlanCacheJoin(t *testing.T) { + store := testkit.CreateMockStore(t) + tk := testkit.NewTestKit(t, store) + tk.MustExec("use test") + tk.MustExec(`set tidb_enable_non_prepared_plan_cache=1`) + tk.MustExec("create table t1 (a int, b int, c int)") + tk.MustExec("create table t2 (a int, b int, c int)") + tk.MustExec("create table t3 (a int, b int, c int)") + + supported := []string{ + "select * from t1, t2 where t1.a=t2.a and t1.b<10", + "select * from t1, t2", + "select * from t1, t2 where t1.a 2 { + return names, false, "queries that have more than 2 tables are not supported" + } + return names, true, "" +} + // nonPreparedPlanCacheableChecker checks whether a query's plan can be cached for non-prepared plan cache. // NOTE: we can add more rules in the future. type nonPreparedPlanCacheableChecker struct { @@ -270,17 +302,18 @@ type nonPreparedPlanCacheableChecker struct { reason string // reason why this statement cannot hit the cache schema infoschema.InfoSchema - tableNode *ast.TableName + tableNodes []*ast.TableName // only support 2-way joins currently + constCnt int // the number of constants/parameters in this query filterCnt int // the number of filters in the current node } -func (checker *nonPreparedPlanCacheableChecker) reset(sctx sessionctx.Context, schema infoschema.InfoSchema, tableNode *ast.TableName) { +func (checker *nonPreparedPlanCacheableChecker) reset(sctx sessionctx.Context, schema infoschema.InfoSchema, tableNodes []*ast.TableName) { checker.sctx = sctx checker.cacheable = true checker.schema = schema checker.reason = "" - checker.tableNode = tableNode + checker.tableNodes = tableNodes checker.constCnt = 0 checker.filterCnt = 0 } @@ -292,19 +325,24 @@ func (checker *nonPreparedPlanCacheableChecker) Enter(in ast.Node) (out ast.Node } switch node := in.(type) { - case *ast.SelectStmt, *ast.FieldList, *ast.SelectField, *ast.TableRefsClause, *ast.Join, *ast.BetweenExpr, + case *ast.SelectStmt, *ast.FieldList, *ast.SelectField, *ast.TableRefsClause, *ast.Join, *ast.BetweenExpr, *ast.OnCondition, *ast.TableSource, *ast.ColumnNameExpr, *ast.PatternInExpr, *ast.BinaryOperationExpr, *ast.ByItem, *ast.AggregateFuncExpr: return in, !checker.cacheable // skip child if un-cacheable case *ast.ColumnName: if checker.filterCnt > 0 { // this column is appearing some filters, e.g. `col = 1` - colType, found := getColType(checker.schema, checker.tableNode, node) - if !found { - checker.cacheable = false - checker.reason = "some column is not found in table schema" - } else if colType == mysql.TypeJSON || colType == mysql.TypeEnum || colType == mysql.TypeSet || colType == mysql.TypeBit { - checker.cacheable = false - checker.reason = "query has some filters with JSON, Enum, Set or Bit columns" + for _, tableNode := range checker.tableNodes { + if tableNode == nil { + continue + } + colType, found := getColType(checker.schema, tableNode, node) + if !found { + checker.cacheable = false + checker.reason = "some column is not found in table schema" + } else if colType == mysql.TypeJSON || colType == mysql.TypeEnum || colType == mysql.TypeSet || colType == mysql.TypeBit { + checker.cacheable = false + checker.reason = "query has some filters with JSON, Enum, Set or Bit columns" + } } } return in, !checker.cacheable @@ -394,6 +432,7 @@ func (checker *nonPreparedPlanCacheableChecker) Enter(in ast.Node) (out ast.Node } return in, !checker.cacheable } + checker.cacheable = false // unexpected cases checker.reason = "query has some unsupported Node" return in, !checker.cacheable diff --git a/planner/core/plan_cacheable_checker_test.go b/planner/core/plan_cacheable_checker_test.go index 49795bd1c8..1d70e244fe 100644 --- a/planner/core/plan_cacheable_checker_test.go +++ b/planner/core/plan_cacheable_checker_test.go @@ -303,6 +303,14 @@ func TestNonPreparedPlanCacheable(t *testing.T) { "select * from test.t where d>now()", // now "select a+1 from test.t where a<13", "select mod(a, 10) from test.t where a<13", + + // 2-way joins + "select * from test.t inner join test.t3 on test.t.a=test.t3.a", + "select * from test.t inner join test.t3 on test.t.a=test.t3.a where test.t.a<10", + "select * from test.t, test.t3", + "select * from test.t, test.t3 where test.t.a=test.t3.a", + "select * from test.t, test.t3 where test.t.a=test.t3.a and test.t.b=t3.b", + "select * from test.t, test.t3 where test.t.a=test.t3.a and test.t.a<10", } unsupported := []string{ @@ -312,7 +320,6 @@ func TestNonPreparedPlanCacheable(t *testing.T) { "select a, sum(b) as c from test.t1 where a > 1 and b < 2 group by a having sum(b) > 1", // having "select * from test.t1 limit 1", // limit "select * from test.t1 order by a", // order by - "select * from test.t1, test.t2", // join "select * from (select * from test.t1) t", // sub-query "insert into test.t1 values(1, 1)", // insert "insert into t1(a, b) select a, b from test.t1", // insert into select