diff --git a/planner/optimize.go b/planner/optimize.go index f19622ee23aa1..7089423cc8973 100644 --- a/planner/optimize.go +++ b/planner/optimize.go @@ -22,6 +22,7 @@ import ( "time" "github.com/pingcap/errors" + "github.com/pingcap/failpoint" "github.com/pingcap/parser" "github.com/pingcap/parser/ast" "github.com/pingcap/tidb/bindinfo" @@ -238,12 +239,16 @@ func optimize(ctx context.Context, sctx sessionctx.Context, node ast.Node, is in sctx.GetSessionVars().RewritePhaseInfo.DurationRewrite = time.Since(beginRewrite) if execPlan, ok := p.(*plannercore.Execute); ok { + failpoint.Inject("optimizeExecuteStmt", nil) execID := execPlan.ExecID if execPlan.Name != "" { execID = sctx.GetSessionVars().PreparedStmtNameToID[execPlan.Name] } if preparedPointer, ok := sctx.GetSessionVars().PreparedStmts[execID]; ok { - if preparedObj, ok := preparedPointer.(*core.CachedPrepareStmt); ok && preparedObj.ForUpdateRead { + // When plan cache is enabled, and it's a for-update read, use the latest schema to detect plan expired. + // Otherwise, do not update schema to avoid plan and execution use different schema versions. + if preparedObj, ok := preparedPointer.(*core.CachedPrepareStmt); ok && preparedObj.ForUpdateRead && + preparedObj.PreparedAst.UseCache { is = domain.GetDomain(sctx).InfoSchema() } } diff --git a/session/pessimistic_test.go b/session/pessimistic_test.go index 07ea9e72b6d6b..f711444b6a880 100644 --- a/session/pessimistic_test.go +++ b/session/pessimistic_test.go @@ -2648,3 +2648,31 @@ func (s *testPessimisticSuite) TestChangeLockToPut(c *C) { tk.MustExec("admin check table t1") } + +func (s *testPessimisticSuite) TestIssue30940(c *C) { + tk := testkit.NewTestKitWithInit(c, s.store) + tk2 := testkit.NewTestKitWithInit(c, s.store) + + tk.MustExec("use test") + tk2.MustExec("use test") + tk.MustExec("drop table if exists t") + tk.MustExec("create table t(id int primary key, v int)") + tk.MustExec("insert into t values(1, 1)") + + stmts := []string{ + "update t set v = v + 1 where id = 1", + "delete from t where id = 1", + } + errCh := make(chan error, 1) + for _, stmt := range stmts { + tk.MustExec(fmt.Sprintf("prepare t from '%s'", stmt)) + tk2.MustExec("alter table t add column a int") + c.Assert(failpoint.Enable("github.com/pingcap/tidb/planner/optimizeExecuteStmt", "pause"), IsNil) + go func() { + errCh <- tk.ExecToErr("execute t") + }() + tk2.MustExec("alter table t drop column a") + c.Assert(failpoint.Disable("github.com/pingcap/tidb/planner/optimizeExecuteStmt"), IsNil) + c.Assert(<-errCh, IsNil) + } +}