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: refactor a few code of plan cache #54404

Merged
merged 3 commits into from
Jul 3, 2024
Merged
Show file tree
Hide file tree
Changes from 2 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
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
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