Skip to content

Commit

Permalink
planner: refactor a few code of plan cache (#54404)
Browse files Browse the repository at this point in the history
ref #54057
  • Loading branch information
qw4990 committed Jul 3, 2024
1 parent aeea03d commit ef53d61
Show file tree
Hide file tree
Showing 6 changed files with 66 additions and 86 deletions.
6 changes: 1 addition & 5 deletions pkg/executor/prepared.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import (

"github.com/pingcap/errors"
"github.com/pingcap/log"
"github.com/pingcap/tidb/pkg/bindinfo"
"github.com/pingcap/tidb/pkg/executor/internal/exec"
"github.com/pingcap/tidb/pkg/expression"
"github.com/pingcap/tidb/pkg/infoschema"
Expand Down Expand Up @@ -208,10 +207,7 @@ func (e *DeallocateExec) Next(context.Context, *chunk.Chunk) error {
}
delete(vars.PreparedStmtNameToID, e.Name)
if e.Ctx().GetSessionVars().EnablePreparedPlanCache {
bindSQL, _ := bindinfo.MatchSQLBindingForPlanCache(e.Ctx(), preparedObj.PreparedAst.Stmt, &preparedObj.BindingInfo)
cacheKey, err := plannercore.NewPlanCacheKey(vars, preparedObj.StmtText, preparedObj.StmtDB, preparedObj.SchemaVersion,
0, bindSQL, expression.ExprPushDownBlackListReloadTimeStamp.Load(),
preparedObj.RelateVersion, e.Ctx().GetSessionVars().StmtCtx.TblInfo2UnionScan)
cacheKey, _, _, err := plannercore.NewPlanCacheKey(e.Ctx(), preparedObj)
if err != nil {
return err
}
Expand Down
49 changes: 10 additions & 39 deletions pkg/planner/core/plan_cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,9 @@ import (
"context"

"github.com/pingcap/errors"
"github.com/pingcap/tidb/pkg/bindinfo"
"github.com/pingcap/tidb/pkg/domain"
"github.com/pingcap/tidb/pkg/expression"
"github.com/pingcap/tidb/pkg/infoschema"
"github.com/pingcap/tidb/pkg/kv"
"github.com/pingcap/tidb/pkg/metrics"
"github.com/pingcap/tidb/pkg/parser/ast"
"github.com/pingcap/tidb/pkg/planner/core/base"
Expand Down Expand Up @@ -211,32 +209,16 @@ func GetPlanFromPlanCache(ctx context.Context, sctx sessionctx.Context,
stmtCtx.WarnSkipPlanCache(stmt.UncacheableReason)
}

var bindSQL string
var binding string
var ignored bool
if stmtCtx.UseCache() {
var ignoreByBinding bool
bindSQL, ignoreByBinding = bindinfo.MatchSQLBindingForPlanCache(sctx, stmt.PreparedAst.Stmt, &stmt.BindingInfo)
if ignoreByBinding {
stmtCtx.SetSkipPlanCache("ignore plan cache by binding")
}
}

// In rc or for update read, we need the latest schema version to decide whether we need to
// rebuild the plan. So we set this value in rc or for update read. In other cases, let it be 0.
var latestSchemaVersion int64

if stmtCtx.UseCache() {
if sctx.GetSessionVars().IsIsolation(ast.ReadCommitted) || stmt.ForUpdateRead {
// In Rc or ForUpdateRead, we should check if the information schema has been changed since
// last time. If it changed, we should rebuild the plan. Here, we use a different and more
// up-to-date schema version which can lead plan cache miss and thus, the plan will be rebuilt.
latestSchemaVersion = domain.GetDomain(sctx).InfoSchema().SchemaMetaVersion()
}
if cacheKey, err = NewPlanCacheKey(sctx.GetSessionVars(), stmt.StmtText,
stmt.StmtDB, stmt.SchemaVersion, latestSchemaVersion, bindSQL,
expression.ExprPushDownBlackListReloadTimeStamp.Load(), stmt.RelateVersion,
stmtCtx.TblInfo2UnionScan); err != nil {
cacheKey, binding, ignored, err = NewPlanCacheKey(sctx, stmt)
if err != nil {
return nil, nil, err
}
if ignored {
stmtCtx.SetSkipPlanCache("ignore plan cache by binding")
}
}

var matchOpts *PlanCacheMatchOpts
Expand All @@ -259,7 +241,7 @@ func GetPlanFromPlanCache(ctx context.Context, sctx sessionctx.Context,
if intest.InTest && ctx.Value(PlanCacheKeyTestBeforeAdjust{}) != nil {
ctx.Value(PlanCacheKeyTestBeforeAdjust{}).(func(cachedVal *PlanCacheValue))(cacheVal.(*PlanCacheValue))
}
if plan, names, ok, err := adjustCachedPlan(sctx, cacheVal.(*PlanCacheValue), isNonPrepared, isPointPlan, bindSQL, is, stmt); err != nil || ok {
if plan, names, ok, err := adjustCachedPlan(sctx, cacheVal.(*PlanCacheValue), isNonPrepared, isPointPlan, binding, is, stmt); err != nil || ok {
if intest.InTest && ctx.Value(PlanCacheKeyTestAfterAdjust{}) != nil {
ctx.Value(PlanCacheKeyTestAfterAdjust{}).(func(cachedVal *PlanCacheValue))(cacheVal.(*PlanCacheValue))
}
Expand All @@ -271,7 +253,7 @@ func GetPlanFromPlanCache(ctx context.Context, sctx sessionctx.Context,
matchOpts = GetMatchOpts(sctx, is, stmt, params)
}

return generateNewPlan(ctx, sctx, isNonPrepared, is, stmt, cacheKey, latestSchemaVersion, bindSQL, matchOpts)
return generateNewPlan(ctx, sctx, isNonPrepared, is, stmt, cacheKey, matchOpts)
}

func adjustCachedPlan(sctx sessionctx.Context, cachedVal *PlanCacheValue, isNonPrepared, isPointPlan bool,
Expand Down Expand Up @@ -306,8 +288,7 @@ func adjustCachedPlan(sctx sessionctx.Context, cachedVal *PlanCacheValue, isNonP
// generateNewPlan call the optimizer to generate a new plan for current statement
// and try to add it to cache
func generateNewPlan(ctx context.Context, sctx sessionctx.Context, isNonPrepared bool, is infoschema.InfoSchema,
stmt *PlanCacheStmt, cacheKey string, latestSchemaVersion int64, bindSQL string,
matchOpts *PlanCacheMatchOpts) (base.Plan, []*types.FieldName, error) {
stmt *PlanCacheStmt, cacheKey string, matchOpts *PlanCacheMatchOpts) (base.Plan, []*types.FieldName, error) {
stmtAst := stmt.PreparedAst
sessVars := sctx.GetSessionVars()
stmtCtx := sessVars.StmtCtx
Expand All @@ -329,16 +310,6 @@ func generateNewPlan(ctx context.Context, sctx sessionctx.Context, isNonPrepared

// put this plan into the plan cache.
if stmtCtx.UseCache() {
// rebuild key to exclude kv.TiFlash when stmt is not read only
if _, isolationReadContainTiFlash := sessVars.IsolationReadEngines[kv.TiFlash]; isolationReadContainTiFlash && !IsReadOnly(stmtAst.Stmt, sessVars) {
delete(sessVars.IsolationReadEngines, kv.TiFlash)
if cacheKey, err = NewPlanCacheKey(sessVars, stmt.StmtText, stmt.StmtDB,
stmt.SchemaVersion, latestSchemaVersion, bindSQL, expression.ExprPushDownBlackListReloadTimeStamp.Load(),
stmt.RelateVersion, stmtCtx.TblInfo2UnionScan); err != nil {
return nil, nil, err
}
sessVars.IsolationReadEngines[kv.TiFlash] = struct{}{}
}
cached := NewPlanCacheValue(p, names, matchOpts, &stmtCtx.StmtHints)
stmt.NormalizedPlan, stmt.PlanDigest = NormalizePlan(p)
stmtCtx.SetPlan(p)
Expand Down
77 changes: 51 additions & 26 deletions pkg/planner/core/plan_cache_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (

"github.com/pingcap/errors"
"github.com/pingcap/tidb/pkg/bindinfo"
"github.com/pingcap/tidb/pkg/domain"
"github.com/pingcap/tidb/pkg/expression"
"github.com/pingcap/tidb/pkg/infoschema"
"github.com/pingcap/tidb/pkg/kv"
Expand Down Expand Up @@ -247,54 +248,78 @@ func hashInt64Uint64Map(b []byte, m map[int64]uint64) []byte {
// Note: lastUpdatedSchemaVersion will only be set in the case of rc or for update read in order to
// differentiate the cache key. In other cases, it will be 0.
// All information that might affect the plan should be considered in this function.
func NewPlanCacheKey(sessionVars *variable.SessionVars, stmtText, stmtDB string,
schemaVersion, lastUpdatedSchemaVersion int64, bindSQL string, exprBlacklistTS int64,
relatedSchemaVersion map[int64]uint64, dirtyTables map[*model.TableInfo]bool) (string, error) {
if stmtText == "" {
return "", errors.New("no statement text")
func NewPlanCacheKey(sctx sessionctx.Context, stmt *PlanCacheStmt) (key, binding string, ignored bool, err error) {
binding, ignored = bindinfo.MatchSQLBindingForPlanCache(sctx, stmt.PreparedAst.Stmt, &stmt.BindingInfo)
if ignored {
return
}

// In rc or for update read, we need the latest schema version to decide whether we need to
// rebuild the plan. So we set this value in rc or for update read. In other cases, let it be 0.
var latestSchemaVersion int64
if sctx.GetSessionVars().IsIsolation(ast.ReadCommitted) || stmt.ForUpdateRead {
// In Rc or ForUpdateRead, we should check if the information schema has been changed since
// last time. If it changed, we should rebuild the plan. Here, we use a different and more
// up-to-date schema version which can lead plan cache miss and thus, the plan will be rebuilt.
latestSchemaVersion = domain.GetDomain(sctx).InfoSchema().SchemaMetaVersion()
}

// rebuild key to exclude kv.TiFlash when stmt is not read only
vars := sctx.GetSessionVars()
if _, isolationReadContainTiFlash := vars.IsolationReadEngines[kv.TiFlash]; isolationReadContainTiFlash && !IsReadOnly(stmt.PreparedAst.Stmt, vars) {
delete(vars.IsolationReadEngines, kv.TiFlash)
defer func() {
vars.IsolationReadEngines[kv.TiFlash] = struct{}{}
}()
}

if stmt.StmtText == "" {
return "", "", false, errors.New("no statement text")
}
if schemaVersion == 0 && !intest.InTest {
return "", errors.New("Schema version uninitialized")
if stmt.SchemaVersion == 0 && !intest.InTest {
return "", "", false, errors.New("Schema version uninitialized")
}
stmtDB := stmt.StmtDB
if stmtDB == "" {
stmtDB = sessionVars.CurrentDB
stmtDB = vars.CurrentDB
}
timezoneOffset := 0
if sessionVars.TimeZone != nil {
_, timezoneOffset = time.Now().In(sessionVars.TimeZone).Zone()
if vars.TimeZone != nil {
_, timezoneOffset = time.Now().In(vars.TimeZone).Zone()
}
_, connCollation := sessionVars.GetCharsetInfo()
_, connCollation := vars.GetCharsetInfo()

hash := make([]byte, 0, len(stmtText)*2) // TODO: a Pool for this
hash := make([]byte, 0, len(stmt.StmtText)*2) // TODO: a Pool for this
hash = append(hash, hack.Slice(stmtDB)...)
hash = codec.EncodeInt(hash, int64(sessionVars.ConnectionID))
hash = append(hash, hack.Slice(stmtText)...)
hash = codec.EncodeInt(hash, schemaVersion)
hash = hashInt64Uint64Map(hash, relatedSchemaVersion)
hash = codec.EncodeInt(hash, int64(vars.ConnectionID))
hash = append(hash, hack.Slice(stmt.StmtText)...)
hash = codec.EncodeInt(hash, stmt.SchemaVersion)
hash = hashInt64Uint64Map(hash, stmt.RelateVersion)
// Only be set in rc or for update read and leave it default otherwise.
// In Rc or ForUpdateRead, we should check whether the information schema has been changed when using plan cache.
// If it changed, we should rebuild the plan. lastUpdatedSchemaVersion help us to decide whether we should rebuild
// the plan in rc or for update read.
hash = codec.EncodeInt(hash, lastUpdatedSchemaVersion)
hash = codec.EncodeInt(hash, int64(sessionVars.SQLMode))
hash = codec.EncodeInt(hash, latestSchemaVersion)
hash = codec.EncodeInt(hash, int64(vars.SQLMode))
hash = codec.EncodeInt(hash, int64(timezoneOffset))
if _, ok := sessionVars.IsolationReadEngines[kv.TiDB]; ok {
if _, ok := vars.IsolationReadEngines[kv.TiDB]; ok {
hash = append(hash, kv.TiDB.Name()...)
}
if _, ok := sessionVars.IsolationReadEngines[kv.TiKV]; ok {
if _, ok := vars.IsolationReadEngines[kv.TiKV]; ok {
hash = append(hash, kv.TiKV.Name()...)
}
if _, ok := sessionVars.IsolationReadEngines[kv.TiFlash]; ok {
if _, ok := vars.IsolationReadEngines[kv.TiFlash]; ok {
hash = append(hash, kv.TiFlash.Name()...)
}
hash = codec.EncodeInt(hash, int64(sessionVars.SelectLimit))
hash = append(hash, hack.Slice(bindSQL)...)
hash = codec.EncodeInt(hash, int64(vars.SelectLimit))
hash = append(hash, hack.Slice(binding)...)
hash = append(hash, hack.Slice(connCollation)...)
hash = append(hash, hack.Slice(strconv.FormatBool(sessionVars.InRestrictedSQL))...)
hash = append(hash, hack.Slice(strconv.FormatBool(vars.InRestrictedSQL))...)
hash = append(hash, hack.Slice(strconv.FormatBool(variable.RestrictedReadOnly.Load()))...)
hash = append(hash, hack.Slice(strconv.FormatBool(variable.VarTiDBSuperReadOnly.Load()))...)
// expr-pushdown-blacklist can affect query optimization, so we need to consider it in plan cache.
hash = codec.EncodeInt(hash, exprBlacklistTS)
hash = codec.EncodeInt(hash, expression.ExprPushDownBlackListReloadTimeStamp.Load())
dirtyTables := vars.StmtCtx.TblInfo2UnionScan
if len(dirtyTables) > 0 {
dirtyTableIDs := make([]int64, 0, len(dirtyTables)) // TODO: a Pool for this
for t, dirty := range dirtyTables {
Expand All @@ -308,7 +333,7 @@ func NewPlanCacheKey(sessionVars *variable.SessionVars, stmtText, stmtDB string,
hash = codec.EncodeInt(hash, id)
}
}
return string(hash), nil
return string(hash), binding, false, nil
}

// PlanCacheValue stores the cached Statement and StmtNode.
Expand Down
1 change: 0 additions & 1 deletion pkg/server/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ go_library(
visibility = ["//visibility:public"],
deps = [
"//pkg/autoid_service",
"//pkg/bindinfo",
"//pkg/config",
"//pkg/domain",
"//pkg/domain/infosync",
Expand Down
6 changes: 1 addition & 5 deletions pkg/server/driver_tidb.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ import (
"strings"

"github.com/pingcap/errors"
"github.com/pingcap/tidb/pkg/bindinfo"
"github.com/pingcap/tidb/pkg/expression"
"github.com/pingcap/tidb/pkg/extension"
"github.com/pingcap/tidb/pkg/kv"
Expand Down Expand Up @@ -201,10 +200,7 @@ func (ts *TiDBStatement) Close() error {
if !ok {
return errors.Errorf("invalid PlanCacheStmt type")
}
bindSQL, _ := bindinfo.MatchSQLBindingForPlanCache(ts.ctx, preparedObj.PreparedAst.Stmt, &preparedObj.BindingInfo)
cacheKey, err := core.NewPlanCacheKey(ts.ctx.GetSessionVars(), preparedObj.StmtText, preparedObj.StmtDB,
preparedObj.SchemaVersion, 0, bindSQL, expression.ExprPushDownBlackListReloadTimeStamp.Load(),
preparedObj.RelateVersion, ts.ctx.GetSessionVars().StmtCtx.TblInfo2UnionScan)
cacheKey, _, _, err := core.NewPlanCacheKey(ts.ctx, preparedObj)
if err != nil {
return err
}
Expand Down
13 changes: 3 additions & 10 deletions pkg/session/session.go
Original file line number Diff line number Diff line change
Expand Up @@ -296,20 +296,15 @@ func (s *session) cleanRetryInfo() {
}

planCacheEnabled := s.GetSessionVars().EnablePreparedPlanCache
var cacheKey, bindSQL string
var cacheKey string
var err error
var preparedObj *plannercore.PlanCacheStmt
var stmtText, stmtDB string
if planCacheEnabled {
firstStmtID := retryInfo.DroppedPreparedStmtIDs[0]
if preparedPointer, ok := s.sessionVars.PreparedStmts[firstStmtID]; ok {
preparedObj, ok = preparedPointer.(*plannercore.PlanCacheStmt)
if ok {
stmtText, stmtDB = preparedObj.StmtText, preparedObj.StmtDB
bindSQL, _ = bindinfo.MatchSQLBindingForPlanCache(s.pctx, preparedObj.PreparedAst.Stmt, &preparedObj.BindingInfo)
cacheKey, err = plannercore.NewPlanCacheKey(s.sessionVars, stmtText, stmtDB, preparedObj.SchemaVersion,
0, bindSQL, expression.ExprPushDownBlackListReloadTimeStamp.Load(),
preparedObj.RelateVersion, s.GetSessionVars().StmtCtx.TblInfo2UnionScan)
cacheKey, _, _, err = plannercore.NewPlanCacheKey(s, preparedObj)
if err != nil {
logutil.Logger(s.currentCtx).Warn("clean cached plan failed", zap.Error(err))
return
Expand All @@ -320,9 +315,7 @@ func (s *session) cleanRetryInfo() {
for i, stmtID := range retryInfo.DroppedPreparedStmtIDs {
if planCacheEnabled {
if i > 0 && preparedObj != nil {
cacheKey, err = plannercore.NewPlanCacheKey(s.sessionVars, stmtText, stmtDB, preparedObj.SchemaVersion,
0, bindSQL, expression.ExprPushDownBlackListReloadTimeStamp.Load(),
preparedObj.RelateVersion, s.GetSessionVars().StmtCtx.TblInfo2UnionScan)
cacheKey, _, _, err = plannercore.NewPlanCacheKey(s, preparedObj)
if err != nil {
logutil.Logger(s.currentCtx).Warn("clean cached plan failed", zap.Error(err))
return
Expand Down

0 comments on commit ef53d61

Please sign in to comment.