From bdc6f4b541a035df49e97d4075babbd71e55598b Mon Sep 17 00:00:00 2001 From: Yuanjia Zhang Date: Tue, 17 Jan 2023 13:29:49 +0800 Subject: [PATCH] planner: refactor to put all plan-cacheability-check functions together (#40625) ref pingcap/tidb#36598 --- planner/core/optimizer.go | 11 ------- planner/core/plan_cache.go | 63 ++++++++++++++++++++++++++++---------- 2 files changed, 47 insertions(+), 27 deletions(-) diff --git a/planner/core/optimizer.go b/planner/core/optimizer.go index 9ea2fdf89c006..c38b8b6f9d39a 100644 --- a/planner/core/optimizer.go +++ b/planner/core/optimizer.go @@ -393,7 +393,6 @@ func postOptimize(ctx context.Context, sctx sessionctx.Context, plan PhysicalPla plan = eliminateUnionScanAndLock(sctx, plan) plan = enableParallelApply(sctx, plan) handleFineGrainedShuffle(ctx, sctx, plan) - checkPlanCacheable(sctx, plan) propagateProbeParents(plan, nil) countStarRewrite(plan) return plan, nil @@ -966,16 +965,6 @@ func setupFineGrainedShuffleInternal(ctx context.Context, sctx sessionctx.Contex } } -// checkPlanCacheable used to check whether a plan can be cached. Plans that -// meet the following characteristics cannot be cached: -// 1. Use the TiFlash engine. -// Todo: make more careful check here. -func checkPlanCacheable(sctx sessionctx.Context, plan PhysicalPlan) { - if sctx.GetSessionVars().StmtCtx.UseCache && useTiFlash(plan) { - sctx.GetSessionVars().StmtCtx.SetSkipPlanCache(errors.Errorf("skip plan-cache: TiFlash plan is un-cacheable")) - } -} - // propagateProbeParents doesn't affect the execution plan, it only sets the probeParents field of a PhysicalPlan. // It's for handling the inconsistency between row count in the statsInfo and the recorded actual row count. Please // see comments in PhysicalPlan for details. diff --git a/planner/core/plan_cache.go b/planner/core/plan_cache.go index 70ae7d3481616..b39208ae7574d 100644 --- a/planner/core/plan_cache.go +++ b/planner/core/plan_cache.go @@ -158,7 +158,7 @@ func GetPlanFromSessionPlanCache(ctx context.Context, sctx sessionctx.Context, } } - paramNum, paramTypes := parseParamTypes(sctx, params) + paramTypes := parseParamTypes(sctx, params) if stmtCtx.UseCache && stmtAst.CachedPlan != nil { // for point query plan if plan, names, ok, err := getCachedPointPlan(stmtAst, sessVars, stmtCtx); ok { @@ -176,12 +176,11 @@ func GetPlanFromSessionPlanCache(ctx context.Context, sctx sessionctx.Context, } } - return generateNewPlan(ctx, sctx, isNonPrepared, is, stmt, cacheKey, latestSchemaVersion, paramNum, paramTypes, bindSQL, limitCountAndOffset) + return generateNewPlan(ctx, sctx, isNonPrepared, is, stmt, cacheKey, latestSchemaVersion, paramTypes, bindSQL, limitCountAndOffset) } // parseParamTypes get parameters' types in PREPARE statement -func parseParamTypes(sctx sessionctx.Context, params []expression.Expression) (paramNum int, paramTypes []*types.FieldType) { - paramNum = len(params) +func parseParamTypes(sctx sessionctx.Context, params []expression.Expression) (paramTypes []*types.FieldType) { for _, param := range params { if c, ok := param.(*expression.Constant); ok { // from binary protocol paramTypes = append(paramTypes, c.GetType()) @@ -267,8 +266,9 @@ func getCachedPlan(sctx sessionctx.Context, isNonPrepared bool, cacheKey kvcache // 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 kvcache.Key, latestSchemaVersion int64, paramNum int, - paramTypes []*types.FieldType, bindSQL string, limitParams []uint64) (Plan, []*types.FieldName, error) { +func generateNewPlan(ctx context.Context, sctx sessionctx.Context, isNonPrepared bool, is infoschema.InfoSchema, + stmt *PlanCacheStmt, cacheKey kvcache.Key, latestSchemaVersion int64, paramTypes []*types.FieldType, + bindSQL string, limitParams []uint64) (Plan, []*types.FieldName, error) { stmtAst := stmt.PreparedAst sessVars := sctx.GetSessionVars() stmtCtx := sessVars.StmtCtx @@ -285,10 +285,10 @@ func generateNewPlan(ctx context.Context, sctx sessionctx.Context, isNonPrepared return nil, nil, err } - // We only cache the tableDual plan when the number of parameters are zero. - if containTableDual(p) && paramNum > 0 { - stmtCtx.SetSkipPlanCache(errors.New("skip plan-cache: get a TableDual plan")) - } + // check whether this plan is cacheable. + checkPlanCacheability(sctx, p, len(paramTypes)) + + // 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) { @@ -309,6 +309,41 @@ func generateNewPlan(ctx context.Context, sctx sessionctx.Context, isNonPrepared return p, names, err } +// checkPlanCacheability checks whether this plan is cacheable and set to skip plan cache if it's uncacheable. +func checkPlanCacheability(sctx sessionctx.Context, p Plan, paramNum int) { + stmtCtx := sctx.GetSessionVars().StmtCtx + var pp PhysicalPlan + switch x := p.(type) { + case *Insert: + pp = x.SelectPlan + case *Update: + pp = x.SelectPlan + case *Delete: + pp = x.SelectPlan + case PhysicalPlan: + pp = x + default: + stmtCtx.SetSkipPlanCache(errors.Errorf("skip plan-cache: unexpected un-cacheable plan %v", p.ExplainID().String())) + return + } + if pp == nil { // simple DML statements + return + } + + if useTiFlash(pp) { + stmtCtx.SetSkipPlanCache(errors.Errorf("skip plan-cache: TiFlash plan is un-cacheable")) + return + } + + // We only cache the tableDual plan when the number of parameters are zero. + if containTableDual(pp) && paramNum > 0 { + stmtCtx.SetSkipPlanCache(errors.New("skip plan-cache: get a TableDual plan")) + return + } + + // TODO: plans accessing MVIndex are un-cacheable +} + // RebuildPlan4CachedPlan will rebuild this plan under current user parameters. func RebuildPlan4CachedPlan(p Plan) error { sc := p.SCtx().GetSessionVars().StmtCtx @@ -678,17 +713,13 @@ func tryCachePointPlan(_ context.Context, sctx sessionctx.Context, return err } -func containTableDual(p Plan) bool { +func containTableDual(p PhysicalPlan) bool { _, isTableDual := p.(*PhysicalTableDual) if isTableDual { return true } - physicalPlan, ok := p.(PhysicalPlan) - if !ok { - return false - } childContainTableDual := false - for _, child := range physicalPlan.Children() { + for _, child := range p.Children() { childContainTableDual = childContainTableDual || containTableDual(child) } return childContainTableDual