Skip to content

Commit 446952c

Browse files
authored
planner: calculate plan_digest in place when fetching plan exec info from stmt_stats in SHOW PLAN FOR <SQL> (#60273)
ref #60148
1 parent 914e4ce commit 446952c

File tree

5 files changed

+120
-2
lines changed

5 files changed

+120
-2
lines changed

pkg/bindinfo/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ go_library(
3232
"//pkg/util/chunk",
3333
"//pkg/util/hack",
3434
"//pkg/util/hint",
35+
"//pkg/util/intest",
3536
"//pkg/util/logutil",
3637
"//pkg/util/parser",
3738
"//pkg/util/sqlexec",

pkg/bindinfo/binding_auto.go

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,18 @@ import (
2020

2121
"github.com/pingcap/errors"
2222
"github.com/pingcap/tidb/pkg/parser"
23+
"github.com/pingcap/tidb/pkg/parser/ast"
2324
"github.com/pingcap/tidb/pkg/sessionctx"
2425
"github.com/pingcap/tidb/pkg/util"
2526
"github.com/pingcap/tidb/pkg/util/chunk"
27+
"github.com/pingcap/tidb/pkg/util/intest"
2628
utilparser "github.com/pingcap/tidb/pkg/util/parser"
2729
"go.uber.org/zap"
2830
)
2931

32+
// PlanDigestFunc is used to get the plan digest of this SQL.
33+
var PlanDigestFunc func(sctx sessionctx.Context, stmt ast.StmtNode) (planDigest string, err error)
34+
3035
// BindingPlanInfo contains the binding info and its corresponding plan execution info, which is used by
3136
// "SHOW PLAN FOR <SQL>" to help users understand the historical plans for a specific SQL.
3237
type BindingPlanInfo struct {
@@ -90,7 +95,22 @@ func (ba *bindingAuto) ShowPlansForSQL(currentDB, sqlOrDigest, charset, collatio
9095
// read plan info from information_schema.tidb_statements_stats
9196
bindingPlans := make([]*BindingPlanInfo, 0, len(bindings))
9297
for _, binding := range bindings {
93-
pInfo, err := ba.getPlanExecInfo(binding.PlanDigest)
98+
if binding.Status == StatusDeleted {
99+
continue
100+
}
101+
102+
planDigest := binding.PlanDigest
103+
if planDigest == "" {
104+
if err := callWithSCtx(ba.sPool, false, func(sctx sessionctx.Context) error {
105+
planDigest = getBindingPlanDigest(sctx, binding.Db, binding.BindSQL)
106+
return nil
107+
}); err != nil {
108+
bindingLogger().Error("get plan digest failed",
109+
zap.String("bind_sql", binding.BindSQL), zap.Error(err))
110+
}
111+
}
112+
113+
pInfo, err := ba.getPlanExecInfo(planDigest)
94114
if err != nil {
95115
bindingLogger().Error("get plan execution info failed", zap.String("plan_digest", binding.PlanDigest), zap.Error(err))
96116
continue
@@ -117,9 +137,13 @@ func (ba *bindingAuto) getPlanExecInfo(planDigest string) (plan *planExecInfo, e
117137
if planDigest == "" {
118138
return nil, nil
119139
}
140+
stmtStatsTable := "information_schema.cluster_tidb_statements_stats"
141+
if intest.InTest { // don't need to access the cluster table in tests.
142+
stmtStatsTable = "information_schema.tidb_statements_stats"
143+
}
120144
stmtQuery := fmt.Sprintf(`select cast(sum(result_rows) as signed), cast(sum(exec_count) as signed),
121145
cast(sum(processed_keys) as signed), cast(sum(total_time) as signed), any_value(plan)
122-
from information_schema.cluster_tidb_statements_stats where plan_digest = '%v'`, planDigest)
146+
from %v where plan_digest = '%v'`, stmtStatsTable, planDigest)
123147

124148
var rows []chunk.Row
125149
err = callWithSCtx(ba.sPool, false, func(sctx sessionctx.Context) error {

pkg/bindinfo/utils.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import (
3030
"github.com/pingcap/tidb/pkg/util/chunk"
3131
"github.com/pingcap/tidb/pkg/util/hint"
3232
"github.com/pingcap/tidb/pkg/util/logutil"
33+
utilparser "github.com/pingcap/tidb/pkg/util/parser"
3334
"github.com/pingcap/tidb/pkg/util/sqlexec"
3435
"go.uber.org/zap"
3536
)
@@ -191,3 +192,36 @@ func newBindingFromStorage(row chunk.Row) *Binding {
191192
PlanDigest: row.GetString(10),
192193
}
193194
}
195+
196+
// getBindingPlanDigest does the best efforts to fill binding's plan_digest.
197+
func getBindingPlanDigest(sctx sessionctx.Context, schema, bindingSQL string) (planDigest string) {
198+
defer func() {
199+
if r := recover(); r != nil {
200+
bindingLogger().Error("panic when filling plan digest for binding",
201+
zap.String("binding_sql", bindingSQL), zap.Reflect("panic", r))
202+
}
203+
}()
204+
205+
vars := sctx.GetSessionVars()
206+
defer func(originalBaseline bool, originalDB string) {
207+
vars.UsePlanBaselines = originalBaseline
208+
vars.CurrentDB = originalDB
209+
}(vars.UsePlanBaselines, vars.CurrentDB)
210+
vars.UsePlanBaselines = false
211+
vars.CurrentDB = schema
212+
213+
p := utilparser.GetParser()
214+
defer utilparser.DestoryParser(p)
215+
p.SetSQLMode(vars.SQLMode)
216+
p.SetParserConfig(vars.BuildParserConfig())
217+
218+
charset, collation := vars.GetCharsetInfo()
219+
if stmt, err := p.ParseOneStmt(bindingSQL, charset, collation); err == nil {
220+
if !hasParam(stmt) {
221+
// if there is '?' from `create binding using select a from t where a=?`,
222+
// the final plan digest might be incorrect.
223+
planDigest, _ = PlanDigestFunc(sctx, stmt)
224+
}
225+
}
226+
return
227+
}

pkg/infoschema/test/clustertablestest/cluster_tables_test.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1254,6 +1254,41 @@ func TestStmtSummaryShowPlanForSQL(t *testing.T) {
12541254
require.Equal(t, result[7], "1") // avg_returned_rows
12551255
}
12561256

1257+
func TestStmtSummaryShowPlanForSQL2(t *testing.T) {
1258+
s := new(clusterTablesSuite)
1259+
s.store, s.dom = testkit.CreateMockStoreAndDomain(t)
1260+
s.rpcserver, s.listenAddr = s.setUpRPCService(t, "127.0.0.1:0", nil)
1261+
s.httpServer, s.mockAddr = s.setUpMockPDHTTPServer()
1262+
s.startTime = time.Now()
1263+
defer s.httpServer.Close()
1264+
defer s.rpcserver.Stop()
1265+
tk := s.newTestKitWithRoot(t)
1266+
require.NoError(t, tk.Session().Auth(&auth.UserIdentity{Username: "root", Hostname: "%"}, nil, nil, nil))
1267+
1268+
tk.MustExec("use test")
1269+
tk.MustExec(`create table t (a int, b int, c varchar(10), key(a), key(b))`)
1270+
1271+
tk.MustExec(`create global binding using select /*+ use_index(t, a) */ a from t where b=1`)
1272+
tk.MustQuery(`select a from test.t where b=1`).Check(testkit.Rows())
1273+
tk.MustQuery(`select @@last_plan_from_binding`).Check(testkit.Rows("1"))
1274+
1275+
rs := tk.MustQuery(`show plan for "select a from test.t where b=1"`).Rows()[0]
1276+
require.Contains(t, rs[2], "index:a(a)")
1277+
require.Equal(t, rs[5], "1") // exec_count
1278+
tk.MustQuery(`select a from test.t where b=1`).Check(testkit.Rows())
1279+
rs = tk.MustQuery(`show plan for "select a from test.t where b=1"`).Rows()[0]
1280+
require.Equal(t, rs[5], "2") // exec_count
1281+
1282+
tk.MustExec(`create global binding using select /*+ use_index(t, b) */ a from test.t where b=1`)
1283+
tk.MustQuery(`select a from test.t where b=1`).Check(testkit.Rows())
1284+
rs = tk.MustQuery(`show plan for "select a from test.t where b=2"`).Rows()[0]
1285+
require.Contains(t, rs[2], "index:b(b)")
1286+
require.Equal(t, rs[5], "1") // exec_count
1287+
tk.MustQuery(`select a from test.t where b=1`).Check(testkit.Rows())
1288+
rs = tk.MustQuery(`show plan for "select a from test.t where b=2"`).Rows()[0]
1289+
require.Equal(t, rs[5], "2") // exec_count
1290+
}
1291+
12571292
func TestCreateBindingFromHistory(t *testing.T) {
12581293
s := new(clusterTablesSuite)
12591294
s.store, s.dom = testkit.CreateMockStoreAndDomain(t)

pkg/planner/optimize.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -594,11 +594,35 @@ func queryPlanCost(sctx sessionctx.Context, stmt ast.StmtNode) (float64, error)
594594
return core.GetPlanCost(pp, property.RootTaskType, optimizetrace.NewDefaultPlanCostOption())
595595
}
596596

597+
func planDigestFunc(sctx sessionctx.Context, stmt ast.StmtNode) (planDigest string, err error) {
598+
ret := &core.PreprocessorReturn{}
599+
nodeW := resolve.NewNodeW(stmt)
600+
err = core.Preprocess(
601+
context.Background(),
602+
sctx,
603+
nodeW,
604+
core.WithPreprocessorReturn(ret),
605+
core.InitTxnContextProvider,
606+
)
607+
if err != nil {
608+
return "", err
609+
}
610+
611+
p, _, err := Optimize(context.Background(), sctx, nodeW, sctx.GetDomainInfoSchema().(infoschema.InfoSchema))
612+
if err != nil {
613+
return "", err
614+
}
615+
flat := core.FlattenPhysicalPlan(p, false)
616+
_, digest := core.NormalizeFlatPlan(flat)
617+
return digest.String(), nil
618+
}
619+
597620
func init() {
598621
core.OptimizeAstNode = Optimize
599622
core.IsReadOnly = IsReadOnly
600623
indexadvisor.QueryPlanCostHook = queryPlanCost
601624
bindinfo.GetBindingHandle = func(sctx sessionctx.Context) bindinfo.BindingHandle {
602625
return domain.GetDomain(sctx).BindingHandle()
603626
}
627+
bindinfo.PlanDigestFunc = planDigestFunc
604628
}

0 commit comments

Comments
 (0)