Skip to content

Commit

Permalink
planner: plan cache supports queries with more than 200 parameters (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
qw4990 authored Jun 21, 2023
1 parent 0c2d07d commit c16b3f6
Show file tree
Hide file tree
Showing 4 changed files with 113 additions and 23 deletions.
27 changes: 14 additions & 13 deletions planner/core/plan_cache_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -638,24 +638,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 @@ -1210,7 +1211,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 @@ -1518,6 +1518,8 @@ var (
TiDBOptFixControl44262 uint64 = 44262
// TiDBOptFixControl44389 controls whether to consider non-point ranges of some CNF item when building ranges.
TiDBOptFixControl44389 uint64 = 44389
// 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

0 comments on commit c16b3f6

Please sign in to comment.