diff --git a/executor/adapter.go b/executor/adapter.go index 3ee5a81cab3e5..bac7780e5230b 100644 --- a/executor/adapter.go +++ b/executor/adapter.go @@ -243,15 +243,7 @@ func (a *ExecStmt) IsPrepared() bool { // If current StmtNode is an ExecuteStmt, we can get its prepared stmt, // then using ast.IsReadOnly function to determine a statement is read only or not. func (a *ExecStmt) IsReadOnly(vars *variable.SessionVars) bool { - if execStmt, ok := a.StmtNode.(*ast.ExecuteStmt); ok { - s, err := getPreparedStmt(execStmt, vars) - if err != nil { - logutil.BgLogger().Error("getPreparedStmt failed", zap.Error(err)) - return false - } - return ast.IsReadOnly(s) - } - return ast.IsReadOnly(a.StmtNode) + return planner.IsReadOnly(a.StmtNode, vars) } // RebuildPlan rebuilds current execute statement plan. diff --git a/executor/executor.go b/executor/executor.go index f30f4ce4dea1b..1421315314ef0 100644 --- a/executor/executor.go +++ b/executor/executor.go @@ -40,6 +40,7 @@ import ( "github.com/pingcap/tidb/kv" "github.com/pingcap/tidb/meta" "github.com/pingcap/tidb/meta/autoid" + "github.com/pingcap/tidb/planner" plannercore "github.com/pingcap/tidb/planner/core" "github.com/pingcap/tidb/privilege" "github.com/pingcap/tidb/sessionctx" @@ -1558,7 +1559,7 @@ func ResetContextOfStmt(ctx sessionctx.Context, s ast.StmtNode) (err error) { sc.MemTracker.SetActionOnExceed(action) } if execStmt, ok := s.(*ast.ExecuteStmt); ok { - s, err = getPreparedStmt(execStmt, vars) + s, err = planner.GetPreparedStmt(execStmt, vars) if err != nil { return } diff --git a/executor/prepared.go b/executor/prepared.go index 2b614b6c405c5..2d360d0673615 100644 --- a/executor/prepared.go +++ b/executor/prepared.go @@ -29,7 +29,6 @@ import ( "github.com/pingcap/tidb/planner" plannercore "github.com/pingcap/tidb/planner/core" "github.com/pingcap/tidb/sessionctx" - "github.com/pingcap/tidb/sessionctx/variable" "github.com/pingcap/tidb/types" driver "github.com/pingcap/tidb/types/parser_driver" "github.com/pingcap/tidb/util" @@ -328,21 +327,3 @@ func CompileExecutePreparedStmt(ctx context.Context, sctx sessionctx.Context, } return stmt, nil } - -func getPreparedStmt(stmt *ast.ExecuteStmt, vars *variable.SessionVars) (ast.StmtNode, error) { - var ok bool - execID := stmt.ExecID - if stmt.Name != "" { - if execID, ok = vars.PreparedStmtNameToID[stmt.Name]; !ok { - return nil, plannercore.ErrStmtNotFound - } - } - if preparedPointer, ok := vars.PreparedStmts[execID]; ok { - preparedObj, ok := preparedPointer.(*plannercore.CachedPrepareStmt) - if !ok { - return nil, errors.Errorf("invalid CachedPrepareStmt type") - } - return preparedObj.PreparedAst.Stmt, nil - } - return nil, plannercore.ErrStmtNotFound -} diff --git a/planner/core/integration_test.go b/planner/core/integration_test.go index 26494242cf09e..de08a3b84176c 100644 --- a/planner/core/integration_test.go +++ b/planner/core/integration_test.go @@ -1056,6 +1056,41 @@ func (s *testIntegrationSuite) TestStreamAggProp(c *C) { } } +func (s *testIntegrationSerialSuite) TestNotReadOnlySQLOnTiFlash(c *C) { + tk := testkit.NewTestKit(c, s.store) + + tk.MustExec("use test") + tk.MustExec("drop table if exists t") + tk.MustExec("create table t (a int, b varchar(20))") + tk.MustExec(`set @@tidb_isolation_read_engines = "tiflash"`) + // Create virtual tiflash replica info. + dom := domain.GetDomain(tk.Se) + is := dom.InfoSchema() + db, exists := is.SchemaByName(model.NewCIStr("test")) + c.Assert(exists, IsTrue) + for _, tblInfo := range db.Tables { + if tblInfo.Name.L == "t" { + tblInfo.TiFlashReplica = &model.TiFlashReplicaInfo{ + Count: 1, + Available: true, + } + } + } + err := tk.ExecToErr("select * from t for update") + c.Assert(err, NotNil) + c.Assert(err.Error(), Equals, `[planner:1815]Internal : Can not find access path matching 'tidb_isolation_read_engines'(value: 'tiflash'). Available values are 'tiflash, tikv'.`) + + err = tk.ExecToErr("insert into t select * from t") + c.Assert(err, NotNil) + c.Assert(err.Error(), Equals, `[planner:1815]Internal : Can not find access path matching 'tidb_isolation_read_engines'(value: 'tiflash'). Available values are 'tiflash, tikv'.`) + + tk.MustExec("prepare stmt_insert from 'insert into t select * from t where t.a = ?'") + tk.MustExec("set @a=1") + err = tk.ExecToErr("execute stmt_insert using @a") + c.Assert(err, NotNil) + c.Assert(err.Error(), Equals, `[planner:1815]Internal : Can not find access path matching 'tidb_isolation_read_engines'(value: 'tiflash'). Available values are 'tiflash, tikv'.`) +} + func (s *testIntegrationSuite) TestSelectLimit(c *C) { tk := testkit.NewTestKit(c, s.store) diff --git a/planner/optimize.go b/planner/optimize.go index f498cd44b80b0..5fe4c09d59de0 100644 --- a/planner/optimize.go +++ b/planner/optimize.go @@ -31,14 +31,58 @@ 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/types" "github.com/pingcap/tidb/util/hint" "github.com/pingcap/tidb/util/logutil" + "go.uber.org/zap" ) +// GetPreparedStmt extract the prepared statement from the execute statement. +func GetPreparedStmt(stmt *ast.ExecuteStmt, vars *variable.SessionVars) (ast.StmtNode, error) { + var ok bool + execID := stmt.ExecID + if stmt.Name != "" { + if execID, ok = vars.PreparedStmtNameToID[stmt.Name]; !ok { + return nil, plannercore.ErrStmtNotFound + } + } + if preparedPointer, ok := vars.PreparedStmts[execID]; ok { + preparedObj, ok := preparedPointer.(*plannercore.CachedPrepareStmt) + if !ok { + return nil, errors.Errorf("invalid CachedPrepareStmt type") + } + return preparedObj.PreparedAst.Stmt, nil + } + return nil, plannercore.ErrStmtNotFound +} + +// IsReadOnly check whether the ast.Node is a read only statement. +func IsReadOnly(node ast.Node, vars *variable.SessionVars) bool { + if execStmt, isExecStmt := node.(*ast.ExecuteStmt); isExecStmt { + s, err := GetPreparedStmt(execStmt, vars) + if err != nil { + logutil.BgLogger().Warn("GetPreparedStmt failed", zap.Error(err)) + return false + } + return ast.IsReadOnly(s) + } + return ast.IsReadOnly(node) +} + // Optimize does optimization and creates a Plan. // The node must be prepared first. func Optimize(ctx context.Context, sctx sessionctx.Context, node ast.Node, is infoschema.InfoSchema) (plannercore.Plan, types.NameSlice, error) { + sessVars := sctx.GetSessionVars() + + // Because for write stmt, TiFlash has a different results when lock the data in point get plan. We ban the TiFlash + // engine in not read only stmt. + if _, isolationReadContainTiFlash := sessVars.IsolationReadEngines[kv.TiFlash]; isolationReadContainTiFlash && !IsReadOnly(node, sessVars) { + delete(sessVars.IsolationReadEngines, kv.TiFlash) + defer func() { + sessVars.IsolationReadEngines[kv.TiFlash] = struct{}{} + }() + } if _, isolationReadContainTiKV := sctx.GetSessionVars().GetIsolationReadEngines()[kv.TiKV]; isolationReadContainTiKV { fp := plannercore.TryFastPlan(sctx, node) if fp != nil { @@ -54,17 +98,17 @@ func Optimize(ctx context.Context, sctx sessionctx.Context, node ast.Node, is in tableHints := hint.ExtractTableHintsFromStmtNode(node, sctx) stmtHints, warns := handleStmtHints(tableHints) defer func() { - sctx.GetSessionVars().StmtCtx.StmtHints = stmtHints + sessVars.StmtCtx.StmtHints = stmtHints for _, warn := range warns { sctx.GetSessionVars().StmtCtx.AppendWarning(warn) } }() - sctx.GetSessionVars().StmtCtx.StmtHints = stmtHints + sessVars.StmtCtx.StmtHints = stmtHints bestPlan, names, _, err := optimize(ctx, sctx, node, is) if err != nil { return nil, nil, err } - if !(sctx.GetSessionVars().UsePlanBaselines || sctx.GetSessionVars().EvolvePlanBaselines) { + if !(sessVars.UsePlanBaselines || sessVars.EvolvePlanBaselines) { return bestPlan, names, nil } stmtNode, ok := node.(ast.StmtNode)