Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

planner: plan cache supports queries with more than 200 parameters (#44829) #44852

Merged
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 14 additions & 13 deletions planner/core/plan_cache_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -650,24 +650,25 @@ func TestPreparedPlanCacheLongInList(t *testing.T) {
return "(" + strings.Join(elements, ",") + ")"
}

tk.MustExec(fmt.Sprintf(`prepare st_99 from 'select * from t where a in %v'`, genInList(99)))
tk.MustExec(`execute st_99`)
tk.MustExec(`execute st_99`)
// the limitation is 200
tk.MustExec(fmt.Sprintf(`prepare st_199 from 'select * from t where a in %v'`, genInList(199)))
tk.MustExec(`execute st_199`)
tk.MustExec(`execute st_199`)
tk.MustQuery(`select @@last_plan_from_cache`).Check(testkit.Rows("1"))

tk.MustExec(fmt.Sprintf(`prepare st_101 from 'select * from t where a in %v'`, genInList(101)))
tk.MustExec(`execute st_101`)
tk.MustExec(`execute st_101`)
tk.MustExec(fmt.Sprintf(`prepare st_201 from 'select * from t where a in %v'`, genInList(201)))
tk.MustExec(`execute st_201`)
tk.MustExec(`execute st_201`)
tk.MustQuery(`select @@last_plan_from_cache`).Check(testkit.Rows("0"))

tk.MustExec(fmt.Sprintf(`prepare st_49_50 from 'select * from t where a in %v and b in %v'`, genInList(49), genInList(50)))
tk.MustExec(`execute st_49_50`)
tk.MustExec(`execute st_49_50`)
tk.MustExec(fmt.Sprintf(`prepare st_99_100 from 'select * from t where a in %v and b in %v'`, genInList(99), genInList(100)))
tk.MustExec(`execute st_99_100`)
tk.MustExec(`execute st_99_100`)
tk.MustQuery(`select @@last_plan_from_cache`).Check(testkit.Rows("1"))

tk.MustExec(fmt.Sprintf(`prepare st_49_52 from 'select * from t where a in %v and b in %v'`, genInList(49), genInList(52)))
tk.MustExec(`execute st_49_52`)
tk.MustExec(`execute st_49_52`)
tk.MustExec(fmt.Sprintf(`prepare st_100_101 from 'select * from t where a in %v and b in %v'`, genInList(100), genInList(101)))
tk.MustExec(`execute st_100_101`)
tk.MustExec(`execute st_100_101`)
tk.MustQuery(`select @@last_plan_from_cache`).Check(testkit.Rows("0"))
}

Expand Down Expand Up @@ -1222,7 +1223,7 @@ func TestLongInsertStmt(t *testing.T) {
tk.MustQuery(`select @@last_plan_from_cache`).Check(testkit.Rows("1"))

tk.MustExec(`prepare inert201 from 'insert into t values (1)` + strings.Repeat(", (1)", 200) + "'")
tk.MustQuery("show warnings").Check(testkit.Rows("Warning 1105 skip prepared plan-cache: too many values (more than 200) in the insert statement"))
tk.MustQuery("show warnings").Check(testkit.Rows("Warning 1105 skip prepared plan-cache: too many values in the insert statement"))
tk.MustExec(`execute inert201`)
tk.MustExec(`execute inert201`)
tk.MustQuery(`select @@last_plan_from_cache`).Check(testkit.Rows("0"))
Expand Down
49 changes: 39 additions & 10 deletions planner/core/plan_cacheable_checker.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ package core

import (
"fmt"
"math"
"strconv"
"sync"

"github.com/pingcap/tidb/expression"
Expand All @@ -26,6 +28,7 @@ import (
"github.com/pingcap/tidb/parser/mysql"
core_metrics "github.com/pingcap/tidb/planner/core/metrics"
"github.com/pingcap/tidb/sessionctx"
"github.com/pingcap/tidb/sessionctx/variable"
"github.com/pingcap/tidb/types"
driver "github.com/pingcap/tidb/types/parser_driver"
"github.com/pingcap/tidb/util/filter"
Expand Down Expand Up @@ -56,6 +59,7 @@ func CacheableWithCtx(sctx sessionctx.Context, node ast.Node, is infoschema.Info
cacheable: true,
schema: is,
sumInListLen: 0,
maxNumParam: getMaxParamLimit(sctx),
}
node.Accept(&checker)
return checker.cacheable, checker.reason
Expand All @@ -69,6 +73,7 @@ type cacheableChecker struct {
reason string // reason why cannot use plan-cache

sumInListLen int // the accumulated number of elements in all in-lists
maxNumParam int
}

// Enter implements Visitor interface.
Expand Down Expand Up @@ -105,9 +110,9 @@ func (checker *cacheableChecker) Enter(in ast.Node) (out ast.Node, skipChildren
if len(node.Lists) > 0 { // avoid index-out-of-range
nCols = len(node.Lists[0])
}
if nRows*nCols > 200 { // to save memory
if nRows*nCols > checker.maxNumParam { // to save memory
checker.cacheable = false
checker.reason = "too many values (more than 200) in the insert statement"
checker.reason = "too many values in the insert statement"
return in, true
}
}
Expand All @@ -120,9 +125,9 @@ func (checker *cacheableChecker) Enter(in ast.Node) (out ast.Node, skipChildren
}
case *ast.PatternInExpr:
checker.sumInListLen += len(node.List)
if checker.sumInListLen > 100 { // to save memory
if checker.sumInListLen > checker.maxNumParam { // to save memory
checker.cacheable = false
checker.reason = "too many values in in-list (more than 100)"
checker.reason = "too many values in in-list"
return in, true
}
case *ast.VariableExpr:
Expand Down Expand Up @@ -223,6 +228,7 @@ func NonPreparedPlanCacheableWithCtx(sctx sessionctx.Context, node ast.Node, is
return false, "not a SELECT statement"
}

maxNumParam := getMaxParamLimit(sctx)
var tableNames []*ast.TableName
switch x := node.(type) {
case *ast.SelectStmt:
Expand Down Expand Up @@ -251,8 +257,8 @@ func NonPreparedPlanCacheableWithCtx(sctx sessionctx.Context, node ast.Node, is
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"
if nRows*nCols > maxNumParam { // to save memory
return false, "too many values in the insert statement"
}
tableNames, ok, reason = extractTableNames(x.Table.TableRefs, tableNames)
if !ok {
Expand Down Expand Up @@ -289,7 +295,7 @@ func NonPreparedPlanCacheableWithCtx(sctx sessionctx.Context, node ast.Node, is

// allocate and init the checker
checker := nonPrepCacheCheckerPool.Get().(*nonPreparedPlanCacheableChecker)
checker.reset(sctx, is, tableNames)
checker.reset(sctx, is, tableNames, maxNumParam)

node.Accept(checker)
cacheable, reason := checker.cacheable, checker.reason
Expand Down Expand Up @@ -382,16 +388,19 @@ type nonPreparedPlanCacheableChecker struct {

constCnt int // the number of constants/parameters in this query
filterCnt int // the number of filters in the current node

maxNumberParam int // the maximum number of parameters for a query to be cached.
}

func (checker *nonPreparedPlanCacheableChecker) reset(sctx sessionctx.Context, schema infoschema.InfoSchema, tableNodes []*ast.TableName) {
func (checker *nonPreparedPlanCacheableChecker) reset(sctx sessionctx.Context, schema infoschema.InfoSchema, tableNodes []*ast.TableName, maxNumberParam int) {
checker.sctx = sctx
checker.cacheable = true
checker.schema = schema
checker.reason = ""
checker.tableNodes = tableNodes
checker.constCnt = 0
checker.filterCnt = 0
checker.maxNumberParam = maxNumberParam
}

// Enter implements Visitor interface.
Expand Down Expand Up @@ -458,9 +467,9 @@ func (checker *nonPreparedPlanCacheableChecker) Enter(in ast.Node) (out ast.Node
checker.reason = "query has null constants"
}
checker.constCnt++
if checker.constCnt > 200 { // just for safety and reduce memory cost
if checker.maxNumberParam > 0 && checker.constCnt > checker.maxNumberParam { // just for safety and reduce memory cost
checker.cacheable = false
checker.reason = "query has more than 200 constants"
checker.reason = "query has too many constants"
}
return in, !checker.cacheable
case *ast.GroupByClause:
Expand Down Expand Up @@ -672,3 +681,23 @@ func isPhysicalPlanCacheable(sctx sessionctx.Context, p PhysicalPlan, paramNum,
}
return true, ""
}

// getMaxParamLimit returns the maximum number of parameters for a query that can be cached in the Plan Cache.
func getMaxParamLimit(sctx sessionctx.Context) int {
v := 200
if sctx == nil || sctx.GetSessionVars() == nil || sctx.GetSessionVars().OptimizerFixControl == nil {
return v
}
if sctx.GetSessionVars().OptimizerFixControl[variable.TiDBOptFixControl44823] != "" {
n, err := strconv.Atoi(sctx.GetSessionVars().OptimizerFixControl[variable.TiDBOptFixControl44823])
if err != nil {
return v
}
if n == 0 {
v = math.MaxInt32 // no limitation
} else if n > 0 {
v = n
}
}
return v
}
58 changes: 58 additions & 0 deletions planner/core/plan_cacheable_checker_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
package core_test

import (
"fmt"
"strings"
"testing"

"github.com/pingcap/tidb/expression"
Expand All @@ -31,6 +33,62 @@ import (
"github.com/stretchr/testify/require"
)

func TestFixControl44823(t *testing.T) {
store := testkit.CreateMockStore(t)
tk := testkit.NewTestKit(t, store)
tk.MustExec("use test")
tk.MustExec(`create table t (a int)`)
var va []string
for i := 0; i < 201; i++ {
tk.MustExec(fmt.Sprintf(`set @a%v = %v`, i, i))
va = append(va, fmt.Sprintf("@a%v", i))
}

// prepared plan cache
tk.MustExec(fmt.Sprintf(`prepare st from 'select * from t where a in (%v?)'`, strings.Repeat("?,", 200)))
tk.MustQuery(`show warnings`).Check(testkit.Rows(`Warning 1105 skip prepared plan-cache: too many values in in-list`))
tk.MustExec(fmt.Sprintf(`execute st using %v`, strings.Join(va, ",")))
tk.MustExec(fmt.Sprintf(`execute st using %v`, strings.Join(va, ",")))
tk.MustQuery(`select @@last_plan_from_cache`).Check(testkit.Rows("0"))

tk.MustExec(`set @@tidb_opt_fix_control = "44823:250"`)
tk.MustExec(fmt.Sprintf(`prepare st from 'select * from t where a in (%v?)'`, strings.Repeat("?,", 200)))
tk.MustQuery(`show warnings`).Check(testkit.Rows()) // no warning
tk.MustExec(fmt.Sprintf(`execute st using %v`, strings.Join(va, ",")))
tk.MustExec(fmt.Sprintf(`execute st using %v`, strings.Join(va, ",")))
tk.MustQuery(`select @@last_plan_from_cache`).Check(testkit.Rows("1")) // can hit

tk.MustExec(`set @@tidb_opt_fix_control = "44823:0"`)
tk.MustExec(fmt.Sprintf(`prepare st from 'select * from t where a in (%v?)'`, strings.Repeat("?,", 200)))
tk.MustQuery(`show warnings`).Check(testkit.Rows()) // no warning
tk.MustExec(fmt.Sprintf(`execute st using %v`, strings.Join(va, ",")))
tk.MustExec(fmt.Sprintf(`execute st using %v`, strings.Join(va, ",")))
tk.MustQuery(`select @@last_plan_from_cache`).Check(testkit.Rows("1"))

// non prepared plan cache
values := make([]string, 0, 201)
for i := 0; i < 201; i++ {
values = append(values, fmt.Sprintf("%v", i))
}
query := fmt.Sprintf("select * from t where a in (%v)", strings.Join(values, ","))
tk.MustExec(`set tidb_enable_non_prepared_plan_cache=1`)

tk.MustExec(`set @@tidb_opt_fix_control = ""`)
tk.MustQuery(query).Check(testkit.Rows())
tk.MustQuery(query).Check(testkit.Rows())
tk.MustQuery(`select @@last_plan_from_cache`).Check(testkit.Rows("0"))

tk.MustExec(`set @@tidb_opt_fix_control = "44823:250"`)
tk.MustQuery(query).Check(testkit.Rows())
tk.MustQuery(query).Check(testkit.Rows())
tk.MustQuery(`select @@last_plan_from_cache`).Check(testkit.Rows("1"))

tk.MustExec(`set @@tidb_opt_fix_control = "44823:0"`)
tk.MustQuery(query).Check(testkit.Rows())
tk.MustQuery(query).Check(testkit.Rows())
tk.MustQuery(`select @@last_plan_from_cache`).Check(testkit.Rows("1"))
}

func TestCacheable(t *testing.T) {
store := testkit.CreateMockStore(t)
mockCtx := mock.NewContext()
Expand Down
2 changes: 2 additions & 0 deletions sessionctx/variable/session.go
Original file line number Diff line number Diff line change
Expand Up @@ -1488,6 +1488,8 @@ var (

// TiDBOptFixControl44262 controls whether to allow to use dynamic-mode to access partitioning tables without global-stats (#44262).
TiDBOptFixControl44262 uint64 = 44262
// TiDBOptFixControl44823 controls the maximum number of parameters for a query that can be cached in the Plan Cache.
TiDBOptFixControl44823 uint64 = 44823
)

// GetOptimizerFixControlValue returns the specified value of the optimizer fix control.
Expand Down