diff --git a/go.sum b/go.sum index 348da90510518..61331453dcc22 100644 --- a/go.sum +++ b/go.sum @@ -524,6 +524,7 @@ github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD github.com/sasha-s/go-deadlock v0.2.0/go.mod h1:StQn567HiB1fF2yJ44N9au7wOhrPS3iZqiDbRupzT10= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/sergi/go-diff v1.0.1-0.20180205163309-da645544ed44/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= +github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/shirou/gopsutil v2.19.10+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= github.com/shirou/gopsutil v3.20.12+incompatible h1:6VEGkOXP/eP4o2Ilk8cSsX0PhOEfX6leqAnD+urrp9M= diff --git a/planner/optimize.go b/planner/optimize.go index 8d8868f2cd38f..06f679f71d199 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 6aa63d87d6600..b131499f74b33 100644 --- a/session/pessimistic_test.go +++ b/session/pessimistic_test.go @@ -2623,3 +2623,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) + } +}