diff --git a/executor/executor.go b/executor/executor.go index 931443b803506..d8f55e39ae8be 100644 --- a/executor/executor.go +++ b/executor/executor.go @@ -1551,6 +1551,9 @@ func ResetContextOfStmt(ctx sessionctx.Context, s ast.StmtNode) (err error) { sc.InExplainStmt = true s = explainStmt.Stmt } + if _, ok := s.(*ast.ExplainForStmt); ok { + sc.InExplainStmt = true + } // TODO: Many same bool variables here. // We should set only two variables ( // IgnoreErr and StrictSQLMode) to avoid setting the same bool variables and diff --git a/executor/explainfor_test.go b/executor/explainfor_test.go index 306ddc65f4593..91f3f538c852a 100644 --- a/executor/explainfor_test.go +++ b/executor/explainfor_test.go @@ -16,11 +16,14 @@ package executor_test import ( "crypto/tls" "fmt" + "math" . "github.com/pingcap/check" "github.com/pingcap/parser/auth" "github.com/pingcap/tidb/planner/core" + "github.com/pingcap/tidb/session" "github.com/pingcap/tidb/util" + "github.com/pingcap/tidb/util/kvcache" "github.com/pingcap/tidb/util/testkit" ) @@ -119,3 +122,35 @@ func (s *testSuite) TestExplainMetricTable(c *C) { tk.MustQuery("desc select * from information_schema.cluster_log where type in ('high_cpu_1','high_memory_1') and time >= '2019-12-23 16:10:13' and time <= '2019-12-23 16:30:13'").Check(testkit.Rows( `MemTableScan_5 10000.00 root table:CLUSTER_LOG start_time:2019-12-23 16:10:13, end_time:2019-12-23 16:30:13, node_types:["high_cpu_1","high_memory_1"]`)) } + +func (s *testSuite) TestExplainForConnPlanCache(c *C) { + tk := testkit.NewTestKit(c, s.store) + orgEnable := core.PreparedPlanCacheEnabled() + defer func() { + core.SetPreparedPlanCache(orgEnable) + }() + core.SetPreparedPlanCache(true) + var err error + tk.Se, err = session.CreateSession4TestWithOpt(s.store, &session.Opt{ + PreparedPlanCache: kvcache.NewSimpleLRUCache(100, 0.1, math.MaxUint64), + }) + c.Assert(err, IsNil) + + tk.MustExec("use test") + tk.MustExec("drop table if exists t") + tk.MustExec("create table t(a int)") + rows := tk.MustQuery("select connection_id()").Rows() + c.Assert(len(rows), Equals, 1) + connID := rows[0][0].(string) + tk.MustExec("prepare stmt from 'select * from t where a = ?'") + tk.MustExec("set @p0='1'") + tk.MustExec("execute stmt using @p0") + tkProcess := tk.Se.ShowProcess() + ps := []*util.ProcessInfo{tkProcess} + tk.Se.SetSessionManager(&mockSessionManager1{PS: ps}) + tk.MustQuery(fmt.Sprintf("explain for connection %s", connID)).Check(testkit.Rows( + "TableReader_7 8000.00 root data:Selection_6", + "└─Selection_6 8000.00 cop[tikv] eq(cast(test.t.a), 1)", + " └─TableFullScan_5 10000.00 cop[tikv] table:t keep order:false, stats:pseudo", + )) +} diff --git a/expression/constant.go b/expression/constant.go index 28bb4a66f0ab2..0cc6d2fe91383 100644 --- a/expression/constant.go +++ b/expression/constant.go @@ -108,11 +108,11 @@ func (c *Constant) Clone() Expression { // GetType implements Expression interface. func (c *Constant) GetType() *types.FieldType { - if c.ParamMarker != nil { + if p := c.ParamMarker; p != nil && !p.ctx.GetSessionVars().StmtCtx.InExplainStmt { // GetType() may be called in multi-threaded context, e.g, in building inner executors of IndexJoin, // so it should avoid data race. We achieve this by returning different FieldType pointer for each call. tp := types.NewFieldType(mysql.TypeUnspecified) - dt := c.ParamMarker.GetUserVar() + dt := p.GetUserVar() types.DefaultParamTypeForValue(dt.GetValue(), tp) return tp } @@ -176,8 +176,13 @@ func (c *Constant) VecEvalJSON(ctx sessionctx.Context, input *chunk.Chunk, resul } func (c *Constant) getLazyDatum() (dt types.Datum, isLazy bool, err error) { - if c.ParamMarker != nil { - dt = c.ParamMarker.GetUserVar() + if p := c.ParamMarker; p != nil { + if p.ctx.GetSessionVars().StmtCtx.InExplainStmt { + // Since `ParamMarker` is not nil only in prepare/execute context, the query must be `explain for connection` when coming here. + // The PreparedParams may have been reset already, to avoid panic, we just use the pre-evaluated datum for this constant. + return dt, false, nil + } + dt = p.GetUserVar() isLazy = true return } else if c.DeferredExpr != nil {