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: allow refineArgs for plan cache in some situations (#28944) #29422

Closed
Closed
Show file tree
Hide file tree
Changes from all 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
921 changes: 921 additions & 0 deletions executor/explainfor_test.go

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion executor/prepared_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -429,7 +429,7 @@ func (s *testSerialSuite) TestIssue28087And28162(c *C) {
tk.MustQuery(`execute stmt using @a,@b,@c`).Check(testkit.Rows("\x01"))
tk.MustExec(`set @a=0x00, @b=0x00, @c=0x01`)
tk.MustQuery(`execute stmt using @a,@b,@c`).Check(testkit.Rows("\x00", "\x01"))
tk.MustQuery(`select @@last_plan_from_cache`).Check(testkit.Rows("1"))
tk.MustQuery(`select @@last_plan_from_cache`).Check(testkit.Rows("0"))

// issue 28162
tk.MustExec(`drop table if exists IDT_MC21780`)
Expand Down
16 changes: 16 additions & 0 deletions executor/testdata/prepare_suite_out.json
Original file line number Diff line number Diff line change
Expand Up @@ -134,10 +134,16 @@
}
],
"Plan": [
<<<<<<< HEAD
"Projection_4 8000.00 root test.t1.a",
"└─IndexReader_10 8000.00 root index:Selection_9",
" └─Selection_9 8000.00 cop[tikv] eq(cast(test.t1.b, double BINARY), 0)",
" └─IndexFullScan_8 10000.00 cop[tikv] table:t1, index:b(b, a) keep order:false, stats:pseudo"
=======
"Projection_4 10.00 root test.t1.a",
"└─IndexReader_6 10.00 root index:IndexRangeScan_5",
" └─IndexRangeScan_5 10.00 cop[tikv] table:t1, index:b(b, a) range:[0,0], keep order:false, stats:pseudo"
>>>>>>> 83e559db0... planner: allow refineArgs for plan cache in some situations (#28944)
],
"LastPlanUseCache": "0",
"Result": null
Expand Down Expand Up @@ -202,13 +208,23 @@
}
],
"Plan": [
<<<<<<< HEAD
"HashJoin_38 63744383.74 root inner join, equal:[eq(test.t1.b, test.t2.b) eq(test.t1.a, test.t2.a)]",
"├─TableReader_59(Build) 7984.01 root data:Selection_58",
"│ └─Selection_58 7984.01 cop[tikv] eq(cast(test.t2.b, double BINARY), 0), not(isnull(test.t2.a)), not(isnull(test.t2.b))",
"│ └─TableFullScan_57 10000.00 cop[tikv] table:t2 keep order:false, stats:pseudo",
"└─TableReader_52(Probe) 7984.01 root data:Selection_51",
" └─Selection_51 7984.01 cop[tikv] eq(cast(test.t1.b, double BINARY), 0), not(isnull(test.t1.a)), not(isnull(test.t1.b))",
" └─TableFullScan_50 10000.00 cop[tikv] table:t1 keep order:false, stats:pseudo"
=======
"HashJoin_36 124.88 root inner join, equal:[eq(test.t1.a, test.t2.a)]",
"├─IndexLookUp_57(Build) 99.90 root ",
"│ ├─IndexRangeScan_55(Build) 99.90 cop[tikv] table:t2, index:b(b, a) range:[0 -inf,0 +inf], keep order:false, stats:pseudo",
"│ └─TableRowIDScan_56(Probe) 99.90 cop[tikv] table:t2 keep order:false, stats:pseudo",
"└─IndexLookUp_51(Probe) 99.90 root ",
" ├─IndexRangeScan_49(Build) 99.90 cop[tikv] table:t1, index:b(b, a) range:[0 -inf,0 +inf], keep order:false, stats:pseudo",
" └─TableRowIDScan_50(Probe) 99.90 cop[tikv] table:t1 keep order:false, stats:pseudo"
>>>>>>> 83e559db0... planner: allow refineArgs for plan cache in some situations (#28944)
],
"LastPlanUseCache": "0",
"Result": null
Expand Down
17 changes: 17 additions & 0 deletions expression/builtin_compare.go
Original file line number Diff line number Diff line change
Expand Up @@ -1360,17 +1360,34 @@ func RefineComparedConstant(ctx sessionctx.Context, targetFieldType types.FieldT
// refineArgs will rewrite the arguments if the compare expression is `int column <cmp> non-int constant` or
// `non-int constant <cmp> int column`. E.g., `a < 1.1` will be rewritten to `a < 2`. It also handles comparing year type
// with int constant if the int constant falls into a sensible year representation.
// This refine operation depends on the values of these args, but these values can change when using plan-cache.
// So we have to skip this operation or mark the plan as over-optimized when using plan-cache.
func (c *compareFunctionClass) refineArgs(ctx sessionctx.Context, args []Expression) []Expression {
<<<<<<< HEAD
if ContainMutableConst(ctx, args) {
return args
}
=======
>>>>>>> 83e559db0... planner: allow refineArgs for plan cache in some situations (#28944)
arg0Type, arg1Type := args[0].GetType(), args[1].GetType()
arg0IsInt := arg0Type.EvalType() == types.ETInt
arg1IsInt := arg1Type.EvalType() == types.ETInt
arg0IsString := arg0Type.EvalType() == types.ETString
arg1IsString := arg1Type.EvalType() == types.ETString
arg0, arg0IsCon := args[0].(*Constant)
arg1, arg1IsCon := args[1].(*Constant)
isExceptional, finalArg0, finalArg1 := false, args[0], args[1]
isPositiveInfinite, isNegativeInfinite := false, false
if MaybeOverOptimized4PlanCache(ctx, args) {
// To keep the result be compatible with MySQL, refine `int non-constant <cmp> str constant`
// here and skip this refine operation in all other cases for safety.
if (arg0IsInt && !arg0IsCon && arg1IsString && arg1IsCon) || (arg1IsInt && !arg1IsCon && arg0IsString && arg0IsCon) {
ctx.GetSessionVars().StmtCtx.MaybeOverOptimized4PlanCache = true
RemoveMutableConst(ctx, args)
} else {
return args
}
}
// int non-constant [cmp] non-int constant
if arg0IsInt && !arg0IsCon && !arg1IsInt && arg1IsCon {
arg1, isExceptional = RefineComparedConstant(ctx, *arg0Type, arg1, c.op)
Expand Down
4 changes: 4 additions & 0 deletions expression/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6267,6 +6267,10 @@ func (s *testIntegrationSerialSuite) TestCacheRefineArgs(c *C) {
tk.MustExec("set @p0='0'")
tk.MustQuery("execute stmt using @p0").Check(testkit.Rows("1"))

tk.MustExec("prepare stmt from 'SELECT UCASE(?) < col_int from t;';")
tk.MustExec("set @a1 = 'xayh7vrWVNqZtzlJmdJQUwAHnkI8Ec';")
tk.MustQuery("execute stmt using @a1;").Check(testkit.Rows("<nil>"))

tk.MustExec("delete from t")
tk.MustExec("insert into t values(1)")
tk.MustExec("prepare stmt from 'SELECT col_int < ? FROM t'")
Expand Down
43 changes: 43 additions & 0 deletions expression/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -879,12 +879,42 @@ func ContainCorrelatedColumn(exprs []Expression) bool {
return false
}

<<<<<<< HEAD
// ContainMutableConst checks if the expressions contain a lazy constant.
func ContainMutableConst(ctx sessionctx.Context, exprs []Expression) bool {
// Treat all constants immutable if plan cache is not enabled for this query.
if !ctx.GetSessionVars().StmtCtx.UseCache {
return false
}
=======
// MaybeOverOptimized4PlanCache used to check whether an optimization can work
// for the statement when we enable the plan cache.
// In some situations, some optimizations maybe over-optimize and cache an
// overOptimized plan. The cached plan may not get the correct result when we
// reuse the plan for other statements.
// For example, `pk>=$a and pk<=$b` can be optimized to a PointGet when
// `$a==$b`, but it will cause wrong results when `$a!=$b`.
// So we need to do the check here. The check includes the following aspects:
// 1. Whether the plan cache switch is enable.
// 2. Whether the statement can be cached.
// 3. Whether the expressions contain a lazy constant.
// TODO: Do more careful check here.
func MaybeOverOptimized4PlanCache(ctx sessionctx.Context, exprs []Expression) bool {
// If we do not enable plan cache, all the optimization can work correctly.
if !ctx.GetSessionVars().StmtCtx.UseCache {
return false
}
if ctx.GetSessionVars().StmtCtx.MaybeOverOptimized4PlanCache {
// If the current statement can not be cached. We should remove the mutable constant.
RemoveMutableConst(ctx, exprs)
return false
}
return containMutableConst(ctx, exprs)
}

// containMutableConst checks if the expressions contain a lazy constant.
func containMutableConst(ctx sessionctx.Context, exprs []Expression) bool {
>>>>>>> 83e559db0... planner: allow refineArgs for plan cache in some situations (#28944)
for _, expr := range exprs {
switch v := expr.(type) {
case *Constant:
Expand All @@ -900,6 +930,19 @@ func ContainMutableConst(ctx sessionctx.Context, exprs []Expression) bool {
return false
}

// RemoveMutableConst used to remove the `ParamMarker` and `DeferredExpr` in the `Constant` expr.
func RemoveMutableConst(ctx sessionctx.Context, exprs []Expression) {
for _, expr := range exprs {
switch v := expr.(type) {
case *Constant:
v.ParamMarker = nil
v.DeferredExpr = nil
case *ScalarFunction:
RemoveMutableConst(ctx, v.GetArgs())
}
}
}

const (
_ = iota
kib = 1 << (10 * iota)
Expand Down
14 changes: 14 additions & 0 deletions planner/core/expression_rewriter.go
Original file line number Diff line number Diff line change
Expand Up @@ -1418,11 +1418,25 @@ func (er *expressionRewriter) inToExpression(lLen int, not bool, tp *types.Field
er.ctxStackAppend(expression.NewNull(), types.EmptyName)
return
}
<<<<<<< HEAD
containMut := expression.ContainMutableConst(er.sctx, args)
if !containMut && leftEt == types.ETInt {
=======
if leftEt == types.ETInt {
>>>>>>> 83e559db0... planner: allow refineArgs for plan cache in some situations (#28944)
for i := 1; i < len(args); i++ {
if c, ok := args[i].(*expression.Constant); ok {
var isExceptional bool
if expression.MaybeOverOptimized4PlanCache(er.sctx, []expression.Expression{c}) {
if c.GetType().EvalType() == types.ETString {
// To keep the result be compatible with MySQL, refine `int non-constant <cmp> str constant`
// here and skip this refine operation in all other cases for safety.
er.sctx.GetSessionVars().StmtCtx.MaybeOverOptimized4PlanCache = true
expression.RemoveMutableConst(er.sctx, []expression.Expression{c})
} else {
continue
}
}
args[i], isExceptional = expression.RefineComparedConstant(er.sctx, *leftFt, c, opcode.EQ)
if isExceptional {
args[i] = c
Expand Down
135 changes: 135 additions & 0 deletions planner/core/prepare_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -925,6 +925,141 @@ func (s *testPlanSerialSuite) TestIssue28254(c *C) {
tk.MustQuery("execute stmt using @a").Check(testkit.Rows("1"))
}

<<<<<<< HEAD
=======
func (s *testPlanSerialSuite) TestIssue28867(c *C) {
defer testleak.AfterTest(c)()
store, dom, err := newStoreWithBootstrap()
c.Assert(err, IsNil)
tk := testkit.NewTestKit(c, store)
orgEnable := core.PreparedPlanCacheEnabled()
defer func() {
dom.Close()
err = store.Close()
c.Assert(err, IsNil)
core.SetPreparedPlanCache(orgEnable)
}()
core.SetPreparedPlanCache(true)

tk.Se, err = session.CreateSession4TestWithOpt(store, &session.Opt{
PreparedPlanCache: kvcache.NewSimpleLRUCache(100, 0.1, math.MaxUint64),
})

tk.MustExec("use test")
tk.MustExec("drop table if exists t1, t2")
tk.MustExec(`CREATE TABLE t1 (c_int int, c_str varchar(40), PRIMARY KEY (c_int, c_str))`)
tk.MustExec(`CREATE TABLE t2 (c_str varchar(40), PRIMARY KEY (c_str))`)
tk.MustExec(`insert into t1 values (1, '1')`)
tk.MustExec(`insert into t2 values ('1')`)

tk.MustExec(`prepare stmt from 'select /*+ INL_JOIN(t1,t2) */ * from t1 join t2 on t1.c_str <= t2.c_str where t1.c_int in (?,?)'`)
tk.MustExec(`set @a=10, @b=20`)
tk.MustQuery(`execute stmt using @a, @b`).Check(testkit.Rows())
tk.MustExec(`set @a=1, @b=2`)
tk.MustQuery(`execute stmt using @a, @b`).Check(testkit.Rows("1 1 1"))

// test case for IndexJoin + PlanCache
tk.MustExec(`drop table t1, t2`)
tk.MustExec(`create table t1 (a int, b int, c int, index idxab(a, b, c))`)
tk.MustExec(`create table t2 (a int, b int)`)

tk.MustExec(`prepare stmt from 'select /*+ INL_JOIN(t1,t2) */ * from t1, t2 where t1.a=t2.a and t1.b=?'`)
tk.MustExec(`set @a=1`)
tk.MustExec(`execute stmt using @a`)
tk.MustExec(`execute stmt using @a`)
// the index range [a, b] depends on parameters, so it cannot use plan-cache
tk.MustQuery("select @@last_plan_from_cache").Check(testkit.Rows("0"))

tk.MustExec(`prepare stmt from 'select /*+ INL_JOIN(t1,t2) */ * from t1, t2 where t1.a=t2.a and t1.c=?'`)
tk.MustExec(`set @a=1`)
tk.MustExec(`execute stmt using @a`)
tk.MustExec(`execute stmt using @a`)
// the index range [a] doesn't depend on parameters, so it can use plan-cache
tk.MustQuery("select @@last_plan_from_cache").Check(testkit.Rows("1"))
}

func (s *testPlanSerialSuite) TestIssue28828(c *C) {
store, dom, err := newStoreWithBootstrap()
c.Assert(err, IsNil)
tk := testkit.NewTestKit(c, store)
defer func() {
dom.Close()
store.Close()
}()
orgEnable := core.PreparedPlanCacheEnabled()
defer func() {
core.SetPreparedPlanCache(orgEnable)
}()
core.SetPreparedPlanCache(true)
tk.MustExec("use test")
tk.MustExec("set @@tidb_enable_collect_execution_info=0;")
tk.MustExec("CREATE TABLE t (" +
"id bigint(20) NOT NULL," +
"audit_id bigint(20) NOT NULL," +
"PRIMARY KEY (id) /*T![clustered_index] CLUSTERED */," +
"KEY index_audit_id (audit_id)" +
");")
tk.MustExec("insert into t values(1,9941971237863475), (2,9941971237863476), (3, 0);")
tk.MustExec("prepare stmt from 'select * from t where audit_id=?';")
tk.MustExec("set @a='9941971237863475', @b=9941971237863475, @c='xayh7vrWVNqZtzlJmdJQUwAHnkI8Ec', @d='0.0', @e='0.1', @f = '9941971237863476';")

tk.MustQuery("execute stmt using @a;").Check(testkit.Rows("1 9941971237863475"))
tk.MustQuery("execute stmt using @b;").Check(testkit.Rows("1 9941971237863475"))
// When the type of parameters have been changed, the plan cache can not be used.
tk.MustQuery("select @@last_plan_from_cache;").Check(testkit.Rows("0"))
tk.MustQuery("execute stmt using @c;").Check(testkit.Rows("3 0"))
tk.MustQuery("select @@last_plan_from_cache;").Check(testkit.Rows("0"))
tk.MustQuery("execute stmt using @d;").Check(testkit.Rows("3 0"))
tk.MustQuery("select @@last_plan_from_cache;").Check(testkit.Rows("0"))
tk.MustQuery("execute stmt using @e;").Check(testkit.Rows())
tk.MustQuery("select @@last_plan_from_cache;").Check(testkit.Rows("0"))
tk.MustQuery("execute stmt using @d;").Check(testkit.Rows("3 0"))
tk.MustQuery("select @@last_plan_from_cache;").Check(testkit.Rows("0"))
tk.MustQuery("execute stmt using @f;").Check(testkit.Rows("2 9941971237863476"))
tk.MustQuery("select @@last_plan_from_cache;").Check(testkit.Rows("0"))
tk.MustExec("prepare stmt from 'select count(*) from t where audit_id in (?, ?, ?, ?, ?)';")
tk.MustQuery("execute stmt using @a, @b, @c, @d, @e;").Check(testkit.Rows("2"))
tk.MustQuery("select @@last_plan_from_cache;").Check(testkit.Rows("0"))
tk.MustQuery("execute stmt using @f, @b, @c, @d, @e;").Check(testkit.Rows("3"))
tk.MustQuery("select @@last_plan_from_cache;").Check(testkit.Rows("0"))
}

func (s *testPlanSerialSuite) TestIssue28920(c *C) {
store, dom, err := newStoreWithBootstrap()
c.Assert(err, IsNil)
tk := testkit.NewTestKit(c, store)
orgEnable := core.PreparedPlanCacheEnabled()
defer func() {
dom.Close()
c.Assert(store.Close(), IsNil)
core.SetPreparedPlanCache(orgEnable)
}()
core.SetPreparedPlanCache(true)

tk.Se, err = session.CreateSession4TestWithOpt(store, &session.Opt{
PreparedPlanCache: kvcache.NewSimpleLRUCache(100, 0.1, math.MaxUint64),
})
c.Assert(err, IsNil)

tk.MustExec(`use test`)
tk.MustExec(`drop table if exists UK_GCOL_VIRTUAL_18928`)
tk.MustExec(`
CREATE TABLE UK_GCOL_VIRTUAL_18928 (
COL102 bigint(20) DEFAULT NULL,
COL103 bigint(20) DEFAULT NULL,
COL1 bigint(20) GENERATED ALWAYS AS (COL102 & 10) VIRTUAL,
COL2 varchar(20) DEFAULT NULL,
COL4 datetime DEFAULT NULL,
COL3 bigint(20) DEFAULT NULL,
COL5 float DEFAULT NULL,
UNIQUE KEY UK_COL1 (COL1))`)
tk.MustExec(`insert into UK_GCOL_VIRTUAL_18928(col102,col2) values("-5175976006730879891", "屘厒镇览錻碛斵大擔觏譨頙硺箄魨搝珄鋧扭趖")`)
tk.MustExec(`prepare stmt from 'SELECT * FROM UK_GCOL_VIRTUAL_18928 WHERE col1 < ? AND col2 != ?'`)
tk.MustExec(`set @a=10, @b="aa"`)
tk.MustQuery(`execute stmt using @a, @b`).Check(testkit.Rows("-5175976006730879891 <nil> 8 屘厒镇览錻碛斵大擔觏譨頙硺箄魨搝珄鋧扭趖 <nil> <nil> <nil>"))
}

>>>>>>> 83e559db0... planner: allow refineArgs for plan cache in some situations (#28944)
func (s *testPlanSerialSuite) TestIssue18066(c *C) {
defer testleak.AfterTest(c)()
store, dom, err := newStoreWithBootstrap()
Expand Down