planner: non-prep plan cache supports DML (#42765)
ref pingcap/tidb#36598
This commit is contained in:
@ -1620,11 +1620,6 @@ func TestNonPreparedPlanExplainWarning(t *testing.T) {
|
||||
"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 (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
|
||||
"update t1 set a = 1 where b = 2", // update
|
||||
"delete from t1 where b = 1", // delete
|
||||
"select * from t1 for update", // lock
|
||||
"select * from t1 where a in (select a from t)", // uncorrelated sub-query
|
||||
"select * from t1 where a in (select a from t where a > t1.a)", // correlated sub-query
|
||||
"select * from t where j < 1", // json
|
||||
@ -1653,11 +1648,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 sub-queries are not supported",
|
||||
"skip non-prepared plan-cache: not a select statement",
|
||||
"skip non-prepared plan-cache: not a select statement",
|
||||
"skip non-prepared plan-cache: not a select statement",
|
||||
"skip non-prepared plan-cache: not a select statement",
|
||||
"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 partitioning table are not supported",
|
||||
"skip non-prepared plan-cache: queries that access partitioning table are not supported",
|
||||
"skip non-prepared plan-cache: query has some filters with JSON, Enum, Set or Bit columns",
|
||||
@ -1740,6 +1730,35 @@ func TestIssue42150(t *testing.T) {
|
||||
tk.MustQuery("select @@last_plan_from_cache").Check(testkit.Rows("1"))
|
||||
}
|
||||
|
||||
func TestNonPreparedPlanCacheDML(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 t (a int default 0, b int default 0)")
|
||||
|
||||
for _, sql := range []string{
|
||||
`select a from t for update`,
|
||||
`select a from t where a<10 for update`,
|
||||
`insert into t values (1, 1)`,
|
||||
`insert into t (a, b) values (1, 1)`,
|
||||
`insert into t (a) values (1)`,
|
||||
`insert into t (b) values (1)`,
|
||||
`insert into t select * from t`,
|
||||
`insert into t select * from t where a>10`,
|
||||
`update t set a=1`,
|
||||
`update t set a=1 where a>10`,
|
||||
`update t set a=1, b=1`,
|
||||
`update t set a=a+1 where a>10`,
|
||||
`delete from t`,
|
||||
`delete from t where a>10`,
|
||||
} {
|
||||
tk.MustExec(sql)
|
||||
tk.MustExec(sql)
|
||||
tk.MustQuery("select @@last_plan_from_cache").Check(testkit.Rows("1"))
|
||||
}
|
||||
}
|
||||
|
||||
func TestNonPreparedPlanCachePanic(t *testing.T) {
|
||||
store := testkit.CreateMockStore(t)
|
||||
tk := testkit.NewTestKit(t, store)
|
||||
@ -1930,3 +1949,18 @@ func BenchmarkPlanCacheInsert(b *testing.B) {
|
||||
tk.MustExec("execute st")
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkNonPreparedPlanCacheDML(b *testing.B) {
|
||||
store := testkit.CreateMockStore(b)
|
||||
tk := testkit.NewTestKit(b, store)
|
||||
tk.MustExec("use test")
|
||||
tk.MustExec("create table t (a int)")
|
||||
tk.MustExec("set tidb_enable_non_prepared_plan_cache=1")
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
tk.MustExec("insert into t values (1)")
|
||||
tk.MustExec("update t set a = 2 where a = 1")
|
||||
tk.MustExec("delete from t where a = 2")
|
||||
}
|
||||
}
|
||||
|
||||
@ -210,35 +210,61 @@ func NonPreparedPlanCacheable(node ast.Node, is infoschema.InfoSchema) bool {
|
||||
|
||||
var nonPrepCacheCheckerPool = &sync.Pool{New: func() any { return &nonPreparedPlanCacheableChecker{} }}
|
||||
|
||||
// NonPreparedPlanCacheableWithCtx checks whether the input ast is cacheable for non-prepared plan cache.
|
||||
// Only support: select {field} from {single-table} where {cond} and {cond} ...
|
||||
// {cond}: {col} {op} {val}
|
||||
// {op}: >, <, =
|
||||
// NonPreparedPlanCacheableWithCtx checks whether this SQL is cacheable for non-prepared plan cache.
|
||||
func NonPreparedPlanCacheableWithCtx(sctx sessionctx.Context, node ast.Node, is infoschema.InfoSchema) (ok bool, reason string) {
|
||||
selectStmt, isSelect := node.(*ast.SelectStmt)
|
||||
if !isSelect { // only support select statement now
|
||||
return false, "not a select statement"
|
||||
}
|
||||
if selectStmt.Kind != ast.SelectStmtKindSelect {
|
||||
return false, "not a select statement"
|
||||
}
|
||||
if len(selectStmt.TableHints) > 0 || // hints
|
||||
selectStmt.Having != nil || // having
|
||||
selectStmt.WindowSpecs != nil || // window function
|
||||
selectStmt.Limit != nil || // limit
|
||||
selectStmt.LockInfo != nil || selectStmt.SelectIntoOpt != nil { // lock info
|
||||
return false, "queries that have hints, aggregation, window-function, order-by, limit and lock are not supported"
|
||||
}
|
||||
from := selectStmt.From
|
||||
if from == nil || selectStmt.From.TableRefs == nil {
|
||||
return false, "queries that have sub-queries are not supported"
|
||||
}
|
||||
tableRefs := from.TableRefs
|
||||
|
||||
// match table names, currently only support 2 tables(2-way join) at most.
|
||||
tableNames, ok, reason := extractTableNames(tableRefs, nil)
|
||||
if !ok {
|
||||
return false, reason
|
||||
var tableNames []*ast.TableName
|
||||
switch x := node.(type) {
|
||||
case *ast.SelectStmt:
|
||||
tableNames, ok, reason = isSelectStmtNonPrepCacheableFastCheck(x)
|
||||
if !ok {
|
||||
return ok, reason
|
||||
}
|
||||
case *ast.UpdateStmt:
|
||||
if x.MultipleTable {
|
||||
return false, "not support multiple tables update statements"
|
||||
}
|
||||
tableNames, ok, reason = extractTableNames(x.TableRefs.TableRefs, tableNames)
|
||||
if !ok {
|
||||
return ok, reason
|
||||
}
|
||||
case *ast.InsertStmt:
|
||||
if x.Select == nil { // `insert into t values (...)`
|
||||
nRows := len(x.Lists)
|
||||
nCols := 0
|
||||
if len(x.Lists) > 0 { // avoid index-out-of-range
|
||||
nCols = len(x.Lists[0])
|
||||
}
|
||||
if nRows*nCols > 200 { // to save memory
|
||||
return false, "too many values (more than 200) in the insert statement"
|
||||
}
|
||||
tableNames, ok, reason = extractTableNames(x.Table.TableRefs, tableNames)
|
||||
if !ok {
|
||||
return ok, reason
|
||||
}
|
||||
} else { // `insert into t select ...`
|
||||
selectStmt, ok := x.Select.(*ast.SelectStmt)
|
||||
if !ok {
|
||||
return false, "not a select statement"
|
||||
}
|
||||
tableNames, ok, reason = isSelectStmtNonPrepCacheableFastCheck(selectStmt)
|
||||
if !ok {
|
||||
return ok, reason
|
||||
}
|
||||
tableNames, ok, reason = extractTableNames(x.Table.TableRefs, tableNames)
|
||||
if !ok {
|
||||
return ok, reason
|
||||
}
|
||||
}
|
||||
case *ast.DeleteStmt:
|
||||
if x.IsMultiTable {
|
||||
return false, "not support multiple tables delete statements"
|
||||
}
|
||||
tableNames, ok, reason = extractTableNames(x.TableRefs.TableRefs, tableNames)
|
||||
if !ok {
|
||||
return ok, reason
|
||||
}
|
||||
default:
|
||||
return false, "not a SELECT/UPDATE/INSERT/DELETE statement"
|
||||
}
|
||||
|
||||
// allocate and init the checker
|
||||
@ -250,10 +276,35 @@ func NonPreparedPlanCacheableWithCtx(sctx sessionctx.Context, node ast.Node, is
|
||||
|
||||
// put the checker back
|
||||
nonPrepCacheCheckerPool.Put(checker)
|
||||
|
||||
return cacheable, reason
|
||||
}
|
||||
|
||||
// isSelectStmtNonPrepCacheableFastCheck checks whether the input select statement is cacheable for non-prepared plan cache.
|
||||
func isSelectStmtNonPrepCacheableFastCheck(selectStmt *ast.SelectStmt) (names []*ast.TableName, ok bool, reason string) {
|
||||
if selectStmt.Kind != ast.SelectStmtKindSelect {
|
||||
return nil, false, "not a select statement"
|
||||
}
|
||||
if len(selectStmt.TableHints) > 0 || // hints
|
||||
selectStmt.Having != nil || // having
|
||||
selectStmt.WindowSpecs != nil || // window function
|
||||
selectStmt.Limit != nil || // limit
|
||||
selectStmt.SelectIntoOpt != nil { // select-into statement
|
||||
return nil, false, "queries that have hints, aggregation, window-function, order-by, limit and lock are not supported"
|
||||
}
|
||||
from := selectStmt.From
|
||||
if from == nil || selectStmt.From.TableRefs == nil {
|
||||
return nil, false, "queries that have sub-queries are not supported"
|
||||
}
|
||||
tableRefs := from.TableRefs
|
||||
|
||||
// match table names, currently only support 2 tables(2-way join) at most.
|
||||
tableNames, ok, reason := extractTableNames(tableRefs, nil)
|
||||
if !ok {
|
||||
return nil, false, reason
|
||||
}
|
||||
return tableNames, true, ""
|
||||
}
|
||||
|
||||
// extractTableNames extracts table names from the input node.
|
||||
// Currently support 2 tables(2-way join) at most.
|
||||
func extractTableNames(node ast.ResultSetNode, names []*ast.TableName) ([]*ast.TableName, bool, string) {
|
||||
@ -326,6 +377,7 @@ 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, *ast.OnCondition,
|
||||
*ast.InsertStmt, *ast.DeleteStmt, *ast.UpdateStmt, *ast.Assignment,
|
||||
*ast.TableSource, *ast.ColumnNameExpr, *ast.PatternInExpr, *ast.BinaryOperationExpr, *ast.ByItem, *ast.AggregateFuncExpr:
|
||||
return in, !checker.cacheable // skip child if un-cacheable
|
||||
case *ast.ColumnName:
|
||||
|
||||
Reference in New Issue
Block a user