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 the function #36603

Merged
merged 20 commits into from
Jul 28, 2022
Merged
Show file tree
Hide file tree
Changes from 17 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
1 change: 1 addition & 0 deletions build/nogo_config.json
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,7 @@
"planner/util": "planner code",
"planner/implementation": "planner code",
"planner/cascades": "planner code",
"planner/core/plan_cache.go": "planner code",
qw4990 marked this conversation as resolved.
Show resolved Hide resolved
"util/": "util code",
"parser/": "parser code",
"meta/": "parser code"
Expand Down
173 changes: 106 additions & 67 deletions planner/core/plan_cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import (
"github.com/pingcap/tidb/privilege"
"github.com/pingcap/tidb/sessionctx"
"github.com/pingcap/tidb/sessionctx/stmtctx"
"github.com/pingcap/tidb/sessionctx/variable"
"github.com/pingcap/tidb/table/tables"
"github.com/pingcap/tidb/types"
"github.com/pingcap/tidb/util/chunk"
Expand Down Expand Up @@ -70,10 +71,29 @@ func GetPlanFromSessionPlanCache(ctx context.Context, sctx sessionctx.Context, i
}
}

var varsNum int
var binVarTypes []byte
var txtVarTypes []*types.FieldType
isBinProtocol := len(binProtoVars) > 0
varsNum, binVarTypes, txtVarTypes := getParamValue(sctx, isBinProtocol, binProtoVars, txtProtoVars)

if prepared.UseCache && prepared.CachedPlan != nil && !ignorePlanCache { // for point query plan
if plan, names, ok, err := getPointQueryPlan(prepared, sessVars, stmtCtx); ok {
return plan, names, err
}
}

if prepared.UseCache && !ignorePlanCache { // for general plans
if plan, names, ok, err := getGeneralPlan(ctx, sctx, cacheKey, bindSQL, is, preparedStmt,
binVarTypes, txtVarTypes); err != nil || ok {
return plan, names, err
}
}

return rebuildPhysicalPlan(ctx, sctx, is, preparedStmt, ignorePlanCache, cacheKey,
latestSchemaVersion, isBinProtocol, varsNum, binVarTypes, txtVarTypes)
}

// getParamValue get real values for "?" in PREPARE statements
func getParamValue(sctx sessionctx.Context, isBinProtocol bool, binProtoVars []types.Datum,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about renaming it to parseParamTypes?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about renaming it to parseParamTypes?

Oops,it's indeed type not value

txtProtoVars []expression.Expression) (varsNum int, binVarTypes []byte, txtVarTypes []*types.FieldType) {
if isBinProtocol { // binary protocol
varsNum = len(binProtoVars)
for _, param := range binProtoVars {
Expand All @@ -90,81 +110,98 @@ func GetPlanFromSessionPlanCache(ctx context.Context, sctx sessionctx.Context, i
txtVarTypes = append(txtVarTypes, tp)
}
}
return
}

if prepared.UseCache && prepared.CachedPlan != nil && !ignorePlanCache { // short path for point-get plans
// Rewriting the expression in the select.where condition will convert its
// type from "paramMarker" to "Constant".When Point Select queries are executed,
// the expression in the where condition will not be evaluated,
// so you don't need to consider whether prepared.useCache is enabled.
plan := prepared.CachedPlan.(Plan)
names := prepared.CachedNames.(types.NameSlice)
err := RebuildPlan4CachedPlan(plan)
if err != nil {
logutil.BgLogger().Debug("rebuild range failed", zap.Error(err))
goto REBUILD
}
if metrics.ResettablePlanCacheCounterFortTest {
metrics.PlanCacheCounter.WithLabelValues("prepare").Inc()
} else {
planCacheCounter.Inc()
}
sessVars.FoundInPlanCache = true
stmtCtx.PointExec = true
return plan, names, nil
func getPointQueryPlan(prepared *ast.Prepared, sessVars *variable.SessionVars, stmtCtx *stmtctx.StatementContext) (Plan,
[]*types.FieldName, bool, error) {
// short path for point-get plans
// Rewriting the expression in the select.where condition will convert its
// type from "paramMarker" to "Constant".When Point Select queries are executed,
// the expression in the where condition will not be evaluated,
// so you don't need to consider whether prepared.useCache is enabled.
plan := prepared.CachedPlan.(Plan)
names := prepared.CachedNames.(types.NameSlice)
err := RebuildPlan4CachedPlan(plan)
if err != nil {
logutil.BgLogger().Debug("rebuild range failed", zap.Error(err))
return nil, nil, false, nil
}
if prepared.UseCache && !ignorePlanCache { // for general plans
if cacheValue, exists := sctx.PreparedPlanCache().Get(cacheKey); exists {
if err := checkPreparedPriv(ctx, sctx, preparedStmt, is); err != nil {
return nil, nil, err
if metrics.ResettablePlanCacheCounterFortTest {
metrics.PlanCacheCounter.WithLabelValues("prepare").Inc()
} else {
planCacheCounter.Inc()
}
sessVars.FoundInPlanCache = true
stmtCtx.PointExec = true
return plan, names, true, nil
}

func getGeneralPlan(ctx context.Context, sctx sessionctx.Context, cacheKey kvcache.Key, bindSQL string,
is infoschema.InfoSchema, preparedStmt *CachedPrepareStmt, binVarTypes []byte, txtVarTypes []*types.FieldType) (Plan,
[]*types.FieldName, bool, error) {
sessVars := sctx.GetSessionVars()
stmtCtx := sessVars.StmtCtx

if cacheValue, exists := sctx.PreparedPlanCache().Get(cacheKey); exists {
if err := checkPreparedPriv(ctx, sctx, preparedStmt, is); err != nil {
return nil, nil, false, err
}
cachedVals := cacheValue.([]*PlanCacheValue)
for _, cachedVal := range cachedVals {
if cachedVal.BindSQL != bindSQL {
// When BindSQL does not match, it means that we have added a new binding,
// and the original cached plan will be invalid,
// so the original cached plan can be cleared directly
sctx.PreparedPlanCache().Delete(cacheKey)
break
}
cachedVals := cacheValue.([]*PlanCacheValue)
for _, cachedVal := range cachedVals {
if cachedVal.BindSQL != bindSQL {
// When BindSQL does not match, it means that we have added a new binding,
// and the original cached plan will be invalid,
// so the original cached plan can be cleared directly
if !cachedVal.varTypesUnchanged(binVarTypes, txtVarTypes) {
continue
}
planValid := true
for tblInfo, unionScan := range cachedVal.TblInfo2UnionScan {
if !unionScan && tableHasDirtyContent(sctx, tblInfo) {
planValid = false
// TODO we can inject UnionScan into cached plan to avoid invalidating it, though
// rebuilding the filters in UnionScan is pretty trivial.
sctx.PreparedPlanCache().Delete(cacheKey)
break
}
if !cachedVal.varTypesUnchanged(binVarTypes, txtVarTypes) {
continue
}
if planValid {
err := RebuildPlan4CachedPlan(cachedVal.Plan)
if err != nil {
logutil.BgLogger().Debug("rebuild range failed", zap.Error(err))
return nil, nil, false, nil
}
planValid := true
for tblInfo, unionScan := range cachedVal.TblInfo2UnionScan {
if !unionScan && tableHasDirtyContent(sctx, tblInfo) {
planValid = false
// TODO we can inject UnionScan into cached plan to avoid invalidating it, though
// rebuilding the filters in UnionScan is pretty trivial.
sctx.PreparedPlanCache().Delete(cacheKey)
break
}
sessVars.FoundInPlanCache = true
if len(bindSQL) > 0 {
// When the `len(bindSQL) > 0`, it means we use the binding.
// So we need to record this.
sessVars.FoundInBinding = true
}
if planValid {
err := RebuildPlan4CachedPlan(cachedVal.Plan)
if err != nil {
logutil.BgLogger().Debug("rebuild range failed", zap.Error(err))
goto REBUILD
}
sessVars.FoundInPlanCache = true
if len(bindSQL) > 0 {
// When the `len(bindSQL) > 0`, it means we use the binding.
// So we need to record this.
sessVars.FoundInBinding = true
}
if metrics.ResettablePlanCacheCounterFortTest {
metrics.PlanCacheCounter.WithLabelValues("prepare").Inc()
} else {
planCacheCounter.Inc()
}
stmtCtx.SetPlanDigest(preparedStmt.NormalizedPlan, preparedStmt.PlanDigest)
return cachedVal.Plan, cachedVal.OutPutNames, nil
if metrics.ResettablePlanCacheCounterFortTest {
metrics.PlanCacheCounter.WithLabelValues("prepare").Inc()
} else {
planCacheCounter.Inc()
}
break
stmtCtx.SetPlanDigest(preparedStmt.NormalizedPlan, preparedStmt.PlanDigest)
return cachedVal.Plan, cachedVal.OutPutNames, true, nil
}
break
}
}
return nil, nil, false, nil
}

func rebuildPhysicalPlan(ctx context.Context, sctx sessionctx.Context, is infoschema.InfoSchema, preparedStmt *CachedPrepareStmt,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function is used to generate a new plan for the current statement if there is no valid plan for it.
So a name like generateNewPlan seems better. Please rename it and add some comments.

ignorePlanCache bool, cacheKey kvcache.Key, latestSchemaVersion int64, isBinProtocol bool, varsNum int, binVarTypes []byte,
txtVarTypes []*types.FieldType) (Plan, []*types.FieldName, error) {
prepared := preparedStmt.PreparedAst
sessVars := sctx.GetSessionVars()
stmtCtx := sessVars.StmtCtx

REBUILD:
planCacheMissCounter.Inc()
stmt := prepared.Stmt
p, names, err := OptimizeAstNode(ctx, sctx, stmt, is)
Expand All @@ -175,6 +212,7 @@ REBUILD:
if err != nil {
return nil, nil, err
}

// We only cache the tableDual plan when the number of vars are zero.
if containTableDual(p) && varsNum > 0 {
stmtCtx.SkipPlanCache = true
Expand Down Expand Up @@ -531,14 +569,15 @@ func tryCachePointPlan(_ context.Context, sctx sessionctx.Context,
err error
names types.NameSlice
)
switch p.(type) {
case *PointGetPlan:

if _, _ok := p.(*PointGetPlan); _ok {
ok, err = IsPointGetWithPKOrUniqueKeyByAutoCommit(sctx, p)
names = p.OutputNames()
if err != nil {
return err
}
}

if ok {
// just cache point plan now
prepared.CachedPlan = p
Expand Down