-
Notifications
You must be signed in to change notification settings - Fork 5.9k
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 Batch/PointGet converted from (primary keys) in ((...), ...) #44838
Changes from all commits
18162c9
24fa732
bbb34b6
307fca3
876fe7e
c262555
64cc165
012db8b
4d5d578
647d487
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -442,7 +442,7 @@ func rebuildRange(p Plan) error { | |
if err != nil { | ||
return err | ||
} | ||
if !isSafeRange(x.AccessConditions, ranges, false, nil) { | ||
if len(ranges.Ranges) != 1 || !isSafeRange(x.AccessConditions, ranges, false, nil) { | ||
return errors.New("rebuild to get an unsafe range") | ||
} | ||
for i := range x.IndexValues { | ||
|
@@ -464,7 +464,7 @@ func rebuildRange(p Plan) error { | |
if err != nil { | ||
return err | ||
} | ||
if !isSafeRange(x.AccessConditions, &ranger.DetachRangeResult{ | ||
if len(ranges) != 1 || !isSafeRange(x.AccessConditions, &ranger.DetachRangeResult{ | ||
Ranges: ranges, | ||
AccessConds: accessConds, | ||
RemainedConds: remainingConds, | ||
|
@@ -533,7 +533,7 @@ func rebuildRange(p Plan) error { | |
if err != nil { | ||
return err | ||
} | ||
if len(ranges) != len(x.Handles) && !isSafeRange(x.AccessConditions, &ranger.DetachRangeResult{ | ||
if len(ranges) != len(x.Handles) || !isSafeRange(x.AccessConditions, &ranger.DetachRangeResult{ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Here we also add more strict restriction for safety? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Here is an example about range length, when the first execution, we got [[1], [2]] from where k in (1, 2), and when the second execution, we got [[1]] from where k in (1, 1). For safety, if the range lengths changed, we generate a new plan instead of re-using the old one. |
||
Ranges: ranges, | ||
AccessConds: accessConds, | ||
RemainedConds: remainingConds, | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -29,6 +29,7 @@ import ( | |
"github.com/pingcap/tidb/parser/ast" | ||
"github.com/pingcap/tidb/parser/model" | ||
"github.com/pingcap/tidb/parser/mysql" | ||
"github.com/pingcap/tidb/planner/util" | ||
"github.com/pingcap/tidb/sessionctx" | ||
"github.com/pingcap/tidb/sessionctx/variable" | ||
"github.com/pingcap/tidb/statistics" | ||
|
@@ -569,3 +570,90 @@ func checkTypesCompatibility4PC(tpsExpected, tpsActual []*types.FieldType) bool | |
} | ||
return true | ||
} | ||
|
||
func isSafePointGetPath4PlanCache(sctx sessionctx.Context, path *util.AccessPath) bool { | ||
// PointGet might contain some over-optimized assumptions, like `a>=1 and a<=1` --> `a=1`, but | ||
// these assumptions may be broken after parameters change. | ||
|
||
if isSafePointGetPath4PlanCacheScenario1(path) { | ||
return true | ||
} | ||
|
||
// TODO: enable this fix control switch by default after more test cases are added. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Need to use |
||
if sctx != nil && sctx.GetSessionVars() != nil && sctx.GetSessionVars().OptimizerFixControl != nil { | ||
v, ok := sctx.GetSessionVars().OptimizerFixControl[variable.TiDBOptFixControl44830] | ||
if ok && variable.TiDBOptOn(v) && (isSafePointGetPath4PlanCacheScenario2(path) || isSafePointGetPath4PlanCacheScenario3(path)) { | ||
return true | ||
} | ||
} | ||
|
||
return false | ||
} | ||
|
||
func isSafePointGetPath4PlanCacheScenario1(path *util.AccessPath) bool { | ||
// safe scenario 1: each column corresponds to a single EQ, `a=1 and b=2 and c=3` --> `[1, 2, 3]` | ||
if len(path.Ranges) <= 0 || path.Ranges[0].Width() != len(path.AccessConds) { | ||
return false | ||
} | ||
for _, accessCond := range path.AccessConds { | ||
f, ok := accessCond.(*expression.ScalarFunction) | ||
if !ok || f.FuncName.L != ast.EQ { // column = constant | ||
return false | ||
} | ||
} | ||
return true | ||
} | ||
|
||
func isSafePointGetPath4PlanCacheScenario2(path *util.AccessPath) bool { | ||
// safe scenario 2: this Batch or PointGet is simply from a single IN predicate, `key in (...)` | ||
if len(path.Ranges) <= 0 || len(path.AccessConds) != 1 { | ||
return false | ||
} | ||
f, ok := path.AccessConds[0].(*expression.ScalarFunction) | ||
if !ok || f.FuncName.L != ast.In { | ||
return false | ||
} | ||
return len(path.Ranges) == len(f.GetArgs())-1 // no duplicated values in this in-list for safety. | ||
} | ||
|
||
func isSafePointGetPath4PlanCacheScenario3(path *util.AccessPath) bool { | ||
// safe scenario 3: this Batch or PointGet is simply from a simple DNF like `key=? or key=? or key=?` | ||
if len(path.Ranges) <= 0 || len(path.AccessConds) != 1 { | ||
return false | ||
} | ||
f, ok := path.AccessConds[0].(*expression.ScalarFunction) | ||
if !ok || f.FuncName.L != ast.LogicOr { | ||
return false | ||
} | ||
|
||
dnfExprs := expression.FlattenDNFConditions(f) | ||
if len(path.Ranges) != len(dnfExprs) { | ||
// no duplicated values in this in-list for safety. | ||
// e.g. `k=1 or k=2 or k=1` --> [[1, 1], [2, 2]] | ||
return false | ||
} | ||
|
||
for _, expr := range dnfExprs { | ||
f, ok := expr.(*expression.ScalarFunction) | ||
if !ok { | ||
return false | ||
} | ||
switch f.FuncName.L { | ||
case ast.EQ: // (k=1 or k=2) --> [k=1, k=2] | ||
case ast.LogicAnd: // ((k1=1 and k2=1) or (k1=2 and k2=2)) --> [k1=1 and k2=1, k2=2 and k2=2] | ||
cnfExprs := expression.FlattenCNFConditions(f) | ||
if path.Ranges[0].Width() != len(cnfExprs) { // not all key columns are specified | ||
return false | ||
} | ||
for _, expr := range cnfExprs { // k1=1 and k2=1 | ||
f, ok := expr.(*expression.ScalarFunction) | ||
if !ok || f.FuncName.L != ast.EQ { | ||
return false | ||
} | ||
} | ||
default: | ||
return false | ||
} | ||
} | ||
return true | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add more restrictions for safety.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For example, when the first execution, we got
[[1], [2]]
fromwhere k in (1, 2)
, and when the second execution, we got[[1]]
fromwhere k in (1, 1)
. For safety, if the range lengths changed, we generate a new plan instead of re-using the old one.