Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
2 changes: 1 addition & 1 deletion pkg/bindinfo/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ go_test(
embed = [":bindinfo"],
flaky = True,
race = "on",
shard_count = 43,
shard_count = 44,
deps = [
"//pkg/parser",
"//pkg/parser/ast",
Expand Down
29 changes: 29 additions & 0 deletions pkg/bindinfo/binding_auto_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (

"github.com/pingcap/tidb/pkg/bindinfo"
"github.com/pingcap/tidb/pkg/parser"
"github.com/pingcap/tidb/pkg/parser/auth"
"github.com/pingcap/tidb/pkg/testkit"
"github.com/pingcap/tidb/pkg/testkit/testdata"
"github.com/stretchr/testify/require"
Expand Down Expand Up @@ -200,6 +201,34 @@ func TestExplainExploreAnalyze(t *testing.T) {
checkExecInfo(`explain explore analyze select count(1) from t where b<10`, true)
}

func TestExplainExploreVerifyAndBind(t *testing.T) {
store := testkit.CreateMockStore(t)
tk := testkit.NewTestKit(t, store)
require.NoError(t, tk.Session().Auth(&auth.UserIdentity{Username: "root", Hostname: "%"}, nil, nil, nil))
tk.MustExec("use test")
tk.MustExec(`create table t (a int, b int, key(a))`)
tk.MustExec(`insert into t values (1, 2), (2, 3), (3, 4), (4, 5)`)

tk.MustQuery(`select * from t`)
tk.MustQuery(`select @@last_plan_from_binding`).Check(testkit.Rows("0"))
require.True(t, len(tk.MustQuery(`show global bindings`).Rows()) == 0) // no binding

rs := tk.MustQuery(`explain explore select * from t`).Rows()
runStmt := rs[0][12].(string) // explain analyze <plan_digest>
bindingStmt := rs[0][13].(string) // create global binding from history using plan digest <plan_digest>

require.True(t, strings.HasPrefix(runStmt, "EXPLAIN ANALYZE"))
require.True(t, strings.HasPrefix(bindingStmt, "CREATE GLOBAL BINDING"))

rs = tk.MustQuery(runStmt).Rows()
require.True(t, strings.Contains(rs[0][0].(string), "TableReader")) // table scan and no error

tk.MustExec(bindingStmt)
tk.MustQuery(`select * from t`)
tk.MustQuery(`select @@last_plan_from_binding`).Check(testkit.Rows("1"))
require.True(t, len(tk.MustQuery(`show global bindings`).Rows()) == 1)
}

func TestPlanGeneration(t *testing.T) {
store := testkit.CreateMockStore(t)
tk := testkit.NewTestKit(t, store)
Expand Down
5 changes: 5 additions & 0 deletions pkg/parser/ast/misc.go
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,8 @@ type ExplainStmt struct {
Explore bool
// SQLDigest to explain, used in `EXPLAIN EXPLORE <sql_digest>`.
SQLDigest string
// PlanDigest to explain, used in `EXPLAIN [ANALYZE] <plan_digest>`.
PlanDigest string
}

// Restore implements Node interface.
Expand Down Expand Up @@ -248,6 +250,9 @@ func (n *ExplainStmt) Restore(ctx *format.RestoreCtx) error {
ctx.WriteString(n.Format)
ctx.WritePlain(" ")
}
if n.PlanDigest != "" {
ctx.WriteString(n.PlanDigest)
}
if n.Stmt != nil {
if err := n.Stmt.Restore(ctx); err != nil {
return errors.Annotate(err, "An error occurred while restore ExplainStmt.Stmt")
Expand Down
10,042 changes: 5,050 additions & 4,992 deletions pkg/parser/parser.go

Large diffs are not rendered by default.

45 changes: 45 additions & 0 deletions pkg/parser/parser.y
Original file line number Diff line number Diff line change
Expand Up @@ -5618,6 +5618,13 @@ ExplainStmt:
Format: "row",
}
}
| ExplainSym stringLit
{
$$ = &ast.ExplainStmt{
PlanDigest: $2,
Format: "row",
}
}
| ExplainSym "FOR" "CONNECTION" NUM
{
$$ = &ast.ExplainForStmt{
Expand Down Expand Up @@ -5653,6 +5660,20 @@ ExplainStmt:
Format: $4,
}
}
| ExplainSym "FORMAT" "=" ExplainFormatType stringLit
{
$$ = &ast.ExplainStmt{
PlanDigest: $5,
Format: $4,
}
}
| ExplainSym "FORMAT" "=" stringLit stringLit
{
$$ = &ast.ExplainStmt{
PlanDigest: $5,
Format: $4,
}
}
| ExplainSym "ANALYZE" ExplainableStmt
{
$$ = &ast.ExplainStmt{
Expand All @@ -5661,6 +5682,14 @@ ExplainStmt:
Analyze: true,
}
}
| ExplainSym "ANALYZE" stringLit
{
$$ = &ast.ExplainStmt{
PlanDigest: $3,
Format: "row",
Analyze: true,
}
}
| ExplainSym "ANALYZE" "FORMAT" "=" ExplainFormatType ExplainableStmt
{
$$ = &ast.ExplainStmt{
Expand All @@ -5669,6 +5698,22 @@ ExplainStmt:
Analyze: true,
}
}
| ExplainSym "ANALYZE" "FORMAT" "=" ExplainFormatType stringLit
{
$$ = &ast.ExplainStmt{
PlanDigest: $6,
Format: $5,
Analyze: true,
}
}
| ExplainSym "ANALYZE" "FORMAT" "=" stringLit stringLit
{
$$ = &ast.ExplainStmt{
PlanDigest: $6,
Format: $5,
Analyze: true,
}
}
| ExplainSym "ANALYZE" "FORMAT" "=" stringLit ExplainableStmt
{
$$ = &ast.ExplainStmt{
Expand Down
4 changes: 4 additions & 0 deletions pkg/parser/parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5706,6 +5706,10 @@ func TestExplain(t *testing.T) {
{"EXPLAIN FORMAT = TIDB_JSON FOR CONNECTION 1", true, "EXPLAIN FORMAT = 'TIDB_JSON' FOR CONNECTION 1"},
{"EXPLAIN FORMAT = tidb_json SELECT 1", true, "EXPLAIN FORMAT = 'tidb_json' SELECT 1"},
{"EXPLAIN ANALYZE FORMAT = tidb_json SELECT 1", true, "EXPLAIN ANALYZE FORMAT = 'tidb_json' SELECT 1"},
{"EXPLAIN 'sqldigest'", true, "EXPLAIN FORMAT = 'row' 'sqldigest'"},
{"EXPLAIN ANALYZE 'sqldigest'", true, "EXPLAIN ANALYZE 'sqldigest'"},
{"EXPLAIN format='json' 'sqldigest'", true, "EXPLAIN FORMAT = 'json' 'sqldigest'"},
{"EXPLAIN ANALYZE format='json' 'sqldigest'", true, "EXPLAIN ANALYZE FORMAT = 'json' 'sqldigest'"},
}
RunTest(t, table, false)
}
Expand Down
1 change: 1 addition & 0 deletions pkg/planner/core/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ go_library(
"//pkg/planner/util/tablesampler",
"//pkg/planner/util/utilfuncp",
"//pkg/privilege",
"//pkg/session/syssession",
"//pkg/sessionctx",
"//pkg/sessionctx/stmtctx",
"//pkg/sessionctx/vardef",
Expand Down
7 changes: 5 additions & 2 deletions pkg/planner/core/common_plans.go
Original file line number Diff line number Diff line change
Expand Up @@ -921,7 +921,8 @@ func (e *Explain) prepareSchema() error {
fieldNames = []string{"TiDB_JSON"}
case e.Explore:
fieldNames = []string{"statement", "binding_hint", "plan", "plan_digest", "avg_latency", "exec_times", "avg_scan_rows",
"avg_returned_rows", "latency_per_returned_row", "scan_rows_per_returned_row", "recommend", "reason"}
"avg_returned_rows", "latency_per_returned_row", "scan_rows_per_returned_row", "recommend", "reason",
"explain_analyze", "binding"}
default:
return errors.Errorf("explain format '%s' is not supported now", e.Format)
}
Expand Down Expand Up @@ -970,7 +971,9 @@ func (e *Explain) renderResultForExplore() error {
strconv.FormatFloat(p.LatencyPerReturnRow, 'f', -1, 64),
strconv.FormatFloat(p.ScanRowsPerReturnRow, 'f', -1, 64),
p.Recommend,
p.Reason})
p.Reason,
fmt.Sprintf("EXPLAIN ANALYZE '%v'", p.PlanDigest),
fmt.Sprintf("CREATE GLOBAL BINDING FROM HISTORY USING PLAN DIGEST '%v'", p.PlanDigest)})
}
return nil
}
Expand Down
49 changes: 47 additions & 2 deletions pkg/planner/core/planbuilder.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ import (
"github.com/pingcap/tidb/pkg/planner/util"
"github.com/pingcap/tidb/pkg/planner/util/domainmisc"
"github.com/pingcap/tidb/pkg/privilege"
"github.com/pingcap/tidb/pkg/session/syssession"
"github.com/pingcap/tidb/pkg/sessionctx"
"github.com/pingcap/tidb/pkg/sessionctx/stmtctx"
"github.com/pingcap/tidb/pkg/sessionctx/vardef"
Expand All @@ -71,6 +72,7 @@ import (
"github.com/pingcap/tidb/pkg/util/dbterror/plannererrors"
"github.com/pingcap/tidb/pkg/util/execdetails"
"github.com/pingcap/tidb/pkg/util/hint"
"github.com/pingcap/tidb/pkg/util/intest"
"github.com/pingcap/tidb/pkg/util/logutil"
utilparser "github.com/pingcap/tidb/pkg/util/parser"
"github.com/pingcap/tidb/pkg/util/ranger"
Expand Down Expand Up @@ -836,9 +838,13 @@ func checkHintedSQL(sql, charset, collation, db string) error {
func fetchRecordFromClusterStmtSummary(sctx base.PlanContext, planDigest string) (schema, query, planHint, charset, collation string, err error) {
ctx := kv.WithInternalSourceType(context.Background(), kv.InternalTxnBindInfo)
exec := sctx.GetSQLExecutor()
prefix := "cluster_"
if intest.InTest {
prefix = ""
}
fields := "stmt_type, schema_name, digest_text, sample_user, prepared, query_sample_text, charset, collation, plan_hint, plan_digest"
sql := fmt.Sprintf("select %s from information_schema.cluster_statements_summary where plan_digest = '%s' union distinct ", fields, planDigest) +
fmt.Sprintf("select %s from information_schema.cluster_statements_summary_history where plan_digest = '%s' ", fields, planDigest) +
sql := fmt.Sprintf("select %s from information_schema.%stidb_statements_stats where plan_digest = '%s' union distinct ", fields, prefix, planDigest) +
fmt.Sprintf("select %s from information_schema.%sstatements_summary_history where plan_digest = '%s' ", fields, prefix, planDigest) +
"order by length(plan_digest) desc"
rs, err := exec.ExecuteInternal(ctx, sql)
if err != nil {
Expand Down Expand Up @@ -5577,11 +5583,50 @@ func (b *PlanBuilder) buildExplainFor(explainFor *ast.ExplainForStmt) (base.Plan
return b.buildExplainPlan(targetPlan, explainForFormat, processInfo.BriefBinaryPlan, false, false, nil, processInfo.RuntimeStatsColl, "")
}

// getHintedStmtThroughPlanDigest gets the hinted SQL like `select /*+ ... */ * from t where ...` from `stmt_summary`
// for `explain [analyze] <plan_digest>` statements.
func getHintedStmtThroughPlanDigest(ctx base.PlanContext, planDigest string) (stmt ast.StmtNode, err error) {
err = domain.GetDomain(ctx).AdvancedSysSessionPool().WithSession(func(se *syssession.Session) error {
return se.WithSessionContext(func(sctx sessionctx.Context) error {
defer func(warnings []stmtctx.SQLWarn) {
sctx.GetSessionVars().StmtCtx.SetWarnings(warnings)
}(sctx.GetSessionVars().StmtCtx.GetWarnings())
// The warnings will be broken in fetchRecordFromClusterStmtSummary(), so we need to save and restore it to make the
// warnings for repeated SQL Digest work.
schema, query, planHint, characterSet, collation, err := fetchRecordFromClusterStmtSummary(sctx.GetPlanCtx(), planDigest)
if err != nil {
return err
}
if query == "" {
return errors.NewNoStackErrorf("can't find any plans for '" + planDigest + "'")
}

p := parser.New()
originNode, err := p.ParseOneStmt(query, characterSet, collation)
if err != nil {
return errors.NewNoStackErrorf("failed to parse SQL for Plan Digest: %v", planDigest)
}
hintedSQL := bindinfo.GenerateBindingSQL(originNode, planHint, schema)
stmt, err = p.ParseOneStmt(hintedSQL, characterSet, collation)
return err
})
})
return
}

func (b *PlanBuilder) buildExplain(ctx context.Context, explain *ast.ExplainStmt) (base.Plan, error) {
if show, ok := explain.Stmt.(*ast.ShowStmt); ok {
return b.buildShow(ctx, show)
}

if explain.PlanDigest != "" { // explain [analyze] <SQLDigest>
hintedStmt, err := getHintedStmtThroughPlanDigest(b.ctx, explain.PlanDigest)
if err != nil {
return nil, err
}
explain.Stmt = hintedStmt
}

sctx, err := AsSctx(b.ctx)
if err != nil {
return nil, err
Expand Down