Skip to content

Commit 17df596

Browse files
authored
planner: prepared plan cache support cached plan with placeholder in limit clause (#40196)
ref #40219
1 parent 4620df6 commit 17df596

9 files changed

+216
-84
lines changed

planner/core/logical_plan_builder.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2017,7 +2017,7 @@ func getUintFromNode(ctx sessionctx.Context, n ast.Node, mustInt64orUint64 bool)
20172017
return 0, false, true
20182018
}
20192019
if mustInt64orUint64 {
2020-
if expected := checkParamTypeInt64orUint64(v); !expected {
2020+
if expected, _ := CheckParamTypeInt64orUint64(v); !expected {
20212021
return 0, false, false
20222022
}
20232023
}
@@ -2054,19 +2054,19 @@ func getUintFromNode(ctx sessionctx.Context, n ast.Node, mustInt64orUint64 bool)
20542054
return 0, false, false
20552055
}
20562056

2057-
// check param type for plan cache limit, only allow int64 and uint64 now
2057+
// CheckParamTypeInt64orUint64 check param type for plan cache limit, only allow int64 and uint64 now
20582058
// eg: set @a = 1;
2059-
func checkParamTypeInt64orUint64(param *driver.ParamMarkerExpr) bool {
2059+
func CheckParamTypeInt64orUint64(param *driver.ParamMarkerExpr) (bool, uint64) {
20602060
val := param.GetValue()
20612061
switch v := val.(type) {
20622062
case int64:
20632063
if v >= 0 {
2064-
return true
2064+
return true, uint64(v)
20652065
}
20662066
case uint64:
2067-
return true
2067+
return true, v
20682068
}
2069-
return false
2069+
return false, 0
20702070
}
20712071

20722072
func extractLimitCountOffset(ctx sessionctx.Context, limit *ast.Limit) (count uint64,

planner/core/plan_cache.go

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -165,15 +165,18 @@ func GetPlanFromSessionPlanCache(ctx context.Context, sctx sessionctx.Context,
165165
return plan, names, err
166166
}
167167
}
168-
168+
limitCountAndOffset, paramErr := ExtractLimitFromAst(stmt.PreparedAst.Stmt, sctx)
169+
if paramErr != nil {
170+
return nil, nil, paramErr
171+
}
169172
if stmtCtx.UseCache { // for non-point plans
170173
if plan, names, ok, err := getCachedPlan(sctx, isNonPrepared, cacheKey, bindSQL, is, stmt,
171-
paramTypes); err != nil || ok {
174+
paramTypes, limitCountAndOffset); err != nil || ok {
172175
return plan, names, err
173176
}
174177
}
175178

176-
return generateNewPlan(ctx, sctx, isNonPrepared, is, stmt, cacheKey, latestSchemaVersion, paramNum, paramTypes, bindSQL)
179+
return generateNewPlan(ctx, sctx, isNonPrepared, is, stmt, cacheKey, latestSchemaVersion, paramNum, paramTypes, bindSQL, limitCountAndOffset)
177180
}
178181

179182
// parseParamTypes get parameters' types in PREPARE statement
@@ -221,12 +224,12 @@ func getCachedPointPlan(stmt *ast.Prepared, sessVars *variable.SessionVars, stmt
221224
}
222225

223226
func getCachedPlan(sctx sessionctx.Context, isNonPrepared bool, cacheKey kvcache.Key, bindSQL string,
224-
is infoschema.InfoSchema, stmt *PlanCacheStmt, paramTypes []*types.FieldType) (Plan,
227+
is infoschema.InfoSchema, stmt *PlanCacheStmt, paramTypes []*types.FieldType, limitParams []uint64) (Plan,
225228
[]*types.FieldName, bool, error) {
226229
sessVars := sctx.GetSessionVars()
227230
stmtCtx := sessVars.StmtCtx
228231

229-
candidate, exist := sctx.GetPlanCache(isNonPrepared).Get(cacheKey, paramTypes)
232+
candidate, exist := sctx.GetPlanCache(isNonPrepared).Get(cacheKey, paramTypes, limitParams)
230233
if !exist {
231234
return nil, nil, false, nil
232235
}
@@ -265,7 +268,7 @@ func getCachedPlan(sctx sessionctx.Context, isNonPrepared bool, cacheKey kvcache
265268
// generateNewPlan call the optimizer to generate a new plan for current statement
266269
// and try to add it to cache
267270
func generateNewPlan(ctx context.Context, sctx sessionctx.Context, isNonPrepared bool, is infoschema.InfoSchema, stmt *PlanCacheStmt, cacheKey kvcache.Key, latestSchemaVersion int64, paramNum int,
268-
paramTypes []*types.FieldType, bindSQL string) (Plan, []*types.FieldName, error) {
271+
paramTypes []*types.FieldType, bindSQL string, limitParams []uint64) (Plan, []*types.FieldName, error) {
269272
stmtAst := stmt.PreparedAst
270273
sessVars := sctx.GetSessionVars()
271274
stmtCtx := sessVars.StmtCtx
@@ -296,11 +299,11 @@ func generateNewPlan(ctx context.Context, sctx sessionctx.Context, isNonPrepared
296299
}
297300
sessVars.IsolationReadEngines[kv.TiFlash] = struct{}{}
298301
}
299-
cached := NewPlanCacheValue(p, names, stmtCtx.TblInfo2UnionScan, paramTypes)
302+
cached := NewPlanCacheValue(p, names, stmtCtx.TblInfo2UnionScan, paramTypes, limitParams)
300303
stmt.NormalizedPlan, stmt.PlanDigest = NormalizePlan(p)
301304
stmtCtx.SetPlan(p)
302305
stmtCtx.SetPlanDigest(stmt.NormalizedPlan, stmt.PlanDigest)
303-
sctx.GetPlanCache(isNonPrepared).Put(cacheKey, cached, paramTypes)
306+
sctx.GetPlanCache(isNonPrepared).Put(cacheKey, cached, paramTypes, limitParams)
304307
}
305308
sessVars.FoundInPlanCache = false
306309
return p, names, err

planner/core/plan_cache_lru.go

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ type LRUPlanCache struct {
5353
lock sync.Mutex
5454

5555
// pickFromBucket get one element from bucket. The LRUPlanCache can not work if it is nil
56-
pickFromBucket func(map[*list.Element]struct{}, []*types.FieldType) (*list.Element, bool)
56+
pickFromBucket func(map[*list.Element]struct{}, []*types.FieldType, []uint64) (*list.Element, bool)
5757
// onEvict will be called if any eviction happened, only for test use now
5858
onEvict func(kvcache.Key, kvcache.Value)
5959

@@ -68,7 +68,7 @@ type LRUPlanCache struct {
6868
// NewLRUPlanCache creates a PCLRUCache object, whose capacity is "capacity".
6969
// NOTE: "capacity" should be a positive value.
7070
func NewLRUPlanCache(capacity uint, guard float64, quota uint64,
71-
pickFromBucket func(map[*list.Element]struct{}, []*types.FieldType) (*list.Element, bool), sctx sessionctx.Context) *LRUPlanCache {
71+
pickFromBucket func(map[*list.Element]struct{}, []*types.FieldType, []uint64) (*list.Element, bool), sctx sessionctx.Context) *LRUPlanCache {
7272
if capacity < 1 {
7373
capacity = 100
7474
logutil.BgLogger().Info("capacity of LRU cache is less than 1, will use default value(100) init cache")
@@ -94,13 +94,13 @@ func strHashKey(key kvcache.Key, deepCopy bool) string {
9494
}
9595

9696
// Get tries to find the corresponding value according to the given key.
97-
func (l *LRUPlanCache) Get(key kvcache.Key, paramTypes []*types.FieldType) (value kvcache.Value, ok bool) {
97+
func (l *LRUPlanCache) Get(key kvcache.Key, paramTypes []*types.FieldType, limitParams []uint64) (value kvcache.Value, ok bool) {
9898
l.lock.Lock()
9999
defer l.lock.Unlock()
100100

101101
bucket, bucketExist := l.buckets[strHashKey(key, false)]
102102
if bucketExist {
103-
if element, exist := l.pickFromBucket(bucket, paramTypes); exist {
103+
if element, exist := l.pickFromBucket(bucket, paramTypes, limitParams); exist {
104104
l.lruList.MoveToFront(element)
105105
return element.Value.(*planCacheEntry).PlanValue, true
106106
}
@@ -109,14 +109,14 @@ func (l *LRUPlanCache) Get(key kvcache.Key, paramTypes []*types.FieldType) (valu
109109
}
110110

111111
// Put puts the (key, value) pair into the LRU Cache.
112-
func (l *LRUPlanCache) Put(key kvcache.Key, value kvcache.Value, paramTypes []*types.FieldType) {
112+
func (l *LRUPlanCache) Put(key kvcache.Key, value kvcache.Value, paramTypes []*types.FieldType, limitParams []uint64) {
113113
l.lock.Lock()
114114
defer l.lock.Unlock()
115115

116116
hash := strHashKey(key, true)
117117
bucket, bucketExist := l.buckets[hash]
118118
if bucketExist {
119-
if element, exist := l.pickFromBucket(bucket, paramTypes); exist {
119+
if element, exist := l.pickFromBucket(bucket, paramTypes, limitParams); exist {
120120
l.updateInstanceMetric(&planCacheEntry{PlanKey: key, PlanValue: value}, element.Value.(*planCacheEntry))
121121
element.Value.(*planCacheEntry).PlanValue = value
122122
l.lruList.MoveToFront(element)
@@ -252,16 +252,36 @@ func (l *LRUPlanCache) memoryControl() {
252252
}
253253

254254
// PickPlanFromBucket pick one plan from bucket
255-
func PickPlanFromBucket(bucket map[*list.Element]struct{}, paramTypes []*types.FieldType) (*list.Element, bool) {
255+
func PickPlanFromBucket(bucket map[*list.Element]struct{}, paramTypes []*types.FieldType, limitParams []uint64) (*list.Element, bool) {
256256
for k := range bucket {
257257
plan := k.Value.(*planCacheEntry).PlanValue.(*PlanCacheValue)
258-
if plan.ParamTypes.CheckTypesCompatibility4PC(paramTypes) {
258+
ok1 := plan.ParamTypes.CheckTypesCompatibility4PC(paramTypes)
259+
if !ok1 {
260+
continue
261+
}
262+
ok2 := checkUint64SliceIfEqual(plan.limitOffsetAndCount, limitParams)
263+
if ok2 {
259264
return k, true
260265
}
261266
}
262267
return nil, false
263268
}
264269

270+
func checkUint64SliceIfEqual(a, b []uint64) bool {
271+
if (a == nil && b != nil) || (a != nil && b == nil) {
272+
return false
273+
}
274+
if len(a) != len(b) {
275+
return false
276+
}
277+
for i := range a {
278+
if a[i] != b[i] {
279+
return false
280+
}
281+
}
282+
return true
283+
}
284+
265285
// updateInstanceMetric update the memory usage and plan num for show in grafana
266286
func (l *LRUPlanCache) updateInstanceMetric(in, out *planCacheEntry) {
267287
updateInstancePlanNum(in, out)

planner/core/plan_cache_lru_test.go

Lines changed: 32 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -65,14 +65,18 @@ func TestLRUPCPut(t *testing.T) {
6565
{types.NewFieldType(mysql.TypeFloat), types.NewFieldType(mysql.TypeLong)},
6666
{types.NewFieldType(mysql.TypeFloat), types.NewFieldType(mysql.TypeInt24)},
6767
}
68+
limitParams := [][]uint64{
69+
{1}, {2}, {3}, {4}, {5},
70+
}
6871

6972
// one key corresponding to multi values
7073
for i := 0; i < 5; i++ {
7174
keys[i] = &planCacheKey{database: strconv.FormatInt(int64(1), 10)}
7275
vals[i] = &PlanCacheValue{
73-
ParamTypes: pTypes[i],
76+
ParamTypes: pTypes[i],
77+
limitOffsetAndCount: limitParams[i],
7478
}
75-
lru.Put(keys[i], vals[i], pTypes[i])
79+
lru.Put(keys[i], vals[i], pTypes[i], limitParams[i])
7680
}
7781
require.Equal(t, lru.size, lru.capacity)
7882
require.Equal(t, uint(3), lru.size)
@@ -103,7 +107,7 @@ func TestLRUPCPut(t *testing.T) {
103107

104108
bucket, exist := lru.buckets[string(hack.String(keys[i].Hash()))]
105109
require.True(t, exist)
106-
element, exist := lru.pickFromBucket(bucket, pTypes[i])
110+
element, exist := lru.pickFromBucket(bucket, pTypes[i], limitParams[i])
107111
require.NotNil(t, element)
108112
require.True(t, exist)
109113
require.Equal(t, root, element)
@@ -131,22 +135,25 @@ func TestLRUPCGet(t *testing.T) {
131135
{types.NewFieldType(mysql.TypeFloat), types.NewFieldType(mysql.TypeLong)},
132136
{types.NewFieldType(mysql.TypeFloat), types.NewFieldType(mysql.TypeInt24)},
133137
}
138+
limitParams := [][]uint64{
139+
{1}, {2}, {3}, {4}, {5},
140+
}
134141
// 5 bucket
135142
for i := 0; i < 5; i++ {
136143
keys[i] = &planCacheKey{database: strconv.FormatInt(int64(i%4), 10)}
137-
vals[i] = &PlanCacheValue{ParamTypes: pTypes[i]}
138-
lru.Put(keys[i], vals[i], pTypes[i])
144+
vals[i] = &PlanCacheValue{ParamTypes: pTypes[i], limitOffsetAndCount: limitParams[i]}
145+
lru.Put(keys[i], vals[i], pTypes[i], limitParams[i])
139146
}
140147

141148
// test for non-existent elements
142149
for i := 0; i < 2; i++ {
143-
value, exists := lru.Get(keys[i], pTypes[i])
150+
value, exists := lru.Get(keys[i], pTypes[i], limitParams[i])
144151
require.False(t, exists)
145152
require.Nil(t, value)
146153
}
147154

148155
for i := 2; i < 5; i++ {
149-
value, exists := lru.Get(keys[i], pTypes[i])
156+
value, exists := lru.Get(keys[i], pTypes[i], limitParams[i])
150157
require.True(t, exists)
151158
require.NotNil(t, value)
152159
require.Equal(t, vals[i], value)
@@ -175,23 +182,29 @@ func TestLRUPCDelete(t *testing.T) {
175182
{types.NewFieldType(mysql.TypeFloat), types.NewFieldType(mysql.TypeEnum)},
176183
{types.NewFieldType(mysql.TypeFloat), types.NewFieldType(mysql.TypeDate)},
177184
}
185+
limitParams := [][]uint64{
186+
{1}, {2}, {3},
187+
}
178188
for i := 0; i < 3; i++ {
179189
keys[i] = &planCacheKey{database: strconv.FormatInt(int64(i), 10)}
180-
vals[i] = &PlanCacheValue{ParamTypes: pTypes[i]}
181-
lru.Put(keys[i], vals[i], pTypes[i])
190+
vals[i] = &PlanCacheValue{
191+
ParamTypes: pTypes[i],
192+
limitOffsetAndCount: limitParams[i],
193+
}
194+
lru.Put(keys[i], vals[i], pTypes[i], []uint64{})
182195
}
183196
require.Equal(t, 3, int(lru.size))
184197

185198
lru.Delete(keys[1])
186-
value, exists := lru.Get(keys[1], pTypes[1])
199+
value, exists := lru.Get(keys[1], pTypes[1], limitParams[1])
187200
require.False(t, exists)
188201
require.Nil(t, value)
189202
require.Equal(t, 2, int(lru.size))
190203

191-
_, exists = lru.Get(keys[0], pTypes[0])
204+
_, exists = lru.Get(keys[0], pTypes[0], limitParams[0])
192205
require.True(t, exists)
193206

194-
_, exists = lru.Get(keys[2], pTypes[2])
207+
_, exists = lru.Get(keys[2], pTypes[2], limitParams[2])
195208
require.True(t, exists)
196209
}
197210

@@ -207,14 +220,14 @@ func TestLRUPCDeleteAll(t *testing.T) {
207220
for i := 0; i < 3; i++ {
208221
keys[i] = &planCacheKey{database: strconv.FormatInt(int64(i), 10)}
209222
vals[i] = &PlanCacheValue{ParamTypes: pTypes[i]}
210-
lru.Put(keys[i], vals[i], pTypes[i])
223+
lru.Put(keys[i], vals[i], pTypes[i], []uint64{})
211224
}
212225
require.Equal(t, 3, int(lru.size))
213226

214227
lru.DeleteAll()
215228

216229
for i := 0; i < 3; i++ {
217-
value, exists := lru.Get(keys[i], pTypes[i])
230+
value, exists := lru.Get(keys[i], pTypes[i], []uint64{})
218231
require.False(t, exists)
219232
require.Nil(t, value)
220233
require.Equal(t, 0, int(lru.size))
@@ -242,7 +255,7 @@ func TestLRUPCSetCapacity(t *testing.T) {
242255
for i := 0; i < 5; i++ {
243256
keys[i] = &planCacheKey{database: strconv.FormatInt(int64(1), 10)}
244257
vals[i] = &PlanCacheValue{ParamTypes: pTypes[i]}
245-
lru.Put(keys[i], vals[i], pTypes[i])
258+
lru.Put(keys[i], vals[i], pTypes[i], []uint64{})
246259
}
247260
require.Equal(t, lru.size, lru.capacity)
248261
require.Equal(t, uint(5), lru.size)
@@ -292,7 +305,7 @@ func TestIssue37914(t *testing.T) {
292305
val := &PlanCacheValue{ParamTypes: pTypes}
293306

294307
require.NotPanics(t, func() {
295-
lru.Put(key, val, pTypes)
308+
lru.Put(key, val, pTypes, []uint64{})
296309
})
297310
}
298311

@@ -313,7 +326,7 @@ func TestIssue38244(t *testing.T) {
313326
for i := 0; i < 5; i++ {
314327
keys[i] = &planCacheKey{database: strconv.FormatInt(int64(i), 10)}
315328
vals[i] = &PlanCacheValue{ParamTypes: pTypes[i]}
316-
lru.Put(keys[i], vals[i], pTypes[i])
329+
lru.Put(keys[i], vals[i], pTypes[i], []uint64{})
317330
}
318331
require.Equal(t, lru.size, lru.capacity)
319332
require.Equal(t, uint(3), lru.size)
@@ -334,15 +347,15 @@ func TestLRUPlanCacheMemoryUsage(t *testing.T) {
334347
for i := 0; i < 3; i++ {
335348
k := randomPlanCacheKey()
336349
v := randomPlanCacheValue(pTypes)
337-
lru.Put(k, v, pTypes)
350+
lru.Put(k, v, pTypes, []uint64{})
338351
res += k.MemoryUsage() + v.MemoryUsage()
339352
require.Equal(t, lru.MemoryUsage(), res)
340353
}
341354
// evict
342355
p := &PhysicalTableScan{}
343356
k := &planCacheKey{database: "3"}
344357
v := &PlanCacheValue{Plan: p}
345-
lru.Put(k, v, pTypes)
358+
lru.Put(k, v, pTypes, []uint64{})
346359
res += k.MemoryUsage() + v.MemoryUsage()
347360
for kk, vv := range evict {
348361
res -= kk.(*planCacheKey).MemoryUsage() + vv.(*PlanCacheValue).MemoryUsage()

0 commit comments

Comments
 (0)