diff --git a/executor/adapter.go b/executor/adapter.go index fa96d82ea5e84..a6aa2aaaccbd7 100644 --- a/executor/adapter.go +++ b/executor/adapter.go @@ -810,7 +810,7 @@ type pessimisticTxn interface { KeysNeedToLock() ([]kv.Key, error) } -// buildExecutor build a executor from plan, prepared statement may need additional procedure. +// buildExecutor build an executor from plan, prepared statement may need additional procedure. func (a *ExecStmt) buildExecutor() (Executor, error) { ctx := a.Ctx stmtCtx := ctx.GetSessionVars().StmtCtx diff --git a/planner/core/planbuilder.go b/planner/core/planbuilder.go index 06b94e42bef9c..c7addcaf222d3 100644 --- a/planner/core/planbuilder.go +++ b/planner/core/planbuilder.go @@ -1069,6 +1069,7 @@ func getPossibleAccessPaths(ctx sessionctx.Context, tableHints *tableHintInfo, i optimizerUseInvisibleIndexes := ctx.GetSessionVars().OptimizerUseInvisibleIndexes + check = check || ctx.GetSessionVars().IsIsolation(ast.ReadCommitted) check = check && ctx.GetSessionVars().ConnectionID > 0 var latestIndexes map[int64]*model.IndexInfo var err error @@ -1576,7 +1577,8 @@ func (b *PlanBuilder) buildPhysicalIndexLookUpReaders(ctx context.Context, dbNam indexInfos := make([]*model.IndexInfo, 0, len(tblInfo.Indices)) indexLookUpReaders := make([]Plan, 0, len(tblInfo.Indices)) - check := b.isForUpdateRead && b.ctx.GetSessionVars().ConnectionID > 0 + check := b.isForUpdateRead || b.ctx.GetSessionVars().IsIsolation(ast.ReadCommitted) + check = check && b.ctx.GetSessionVars().ConnectionID > 0 var latestIndexes map[int64]*model.IndexInfo var err error diff --git a/planner/core/point_get_plan.go b/planner/core/point_get_plan.go index 1421aadb3f45c..2fe01b66d26f3 100644 --- a/planner/core/point_get_plan.go +++ b/planner/core/point_get_plan.go @@ -915,6 +915,7 @@ func tryPointGetPlan(ctx sessionctx.Context, selStmt *ast.SelectStmt, check bool return nil } + check = check || ctx.GetSessionVars().IsIsolation(ast.ReadCommitted) check = check && ctx.GetSessionVars().ConnectionID > 0 var latestIndexes map[int64]*model.IndexInfo var err error diff --git a/planner/core/prepare_test.go b/planner/core/prepare_test.go index baf701af22a0e..5ee199b2477c7 100644 --- a/planner/core/prepare_test.go +++ b/planner/core/prepare_test.go @@ -33,6 +33,7 @@ import ( "github.com/pingcap/tidb/planner/core" "github.com/pingcap/tidb/session" "github.com/pingcap/tidb/sessionctx/variable" + "github.com/pingcap/tidb/types" "github.com/pingcap/tidb/util/hint" "github.com/pingcap/tidb/util/israce" "github.com/pingcap/tidb/util/kvcache" @@ -2694,3 +2695,52 @@ func (s *testPlanSerialSuite) TestPartitionWithVariedDatasources(c *C) { } } } + +func (s *testPlanSerialSuite) TestPlanCacheWithRCWhenInfoSchemaChange(c *C) { + store, dom, err := newStoreWithBootstrap() + orgEnable := core.PreparedPlanCacheEnabled() + defer func() { + dom.Close() + err = store.Close() + c.Assert(err, IsNil) + core.SetPreparedPlanCache(orgEnable) + }() + core.SetPreparedPlanCache(true) + + ctx := context.Background() + + tk1 := testkit.NewTestKit(c, store) + tk2 := testkit.NewTestKit(c, store) + tk1.MustExec("use test") + tk2.MustExec("use test") + tk1.MustExec("drop table if exists t1") + tk1.MustExec("create table t1(id int primary key, c int, index ic (c))") + // prepare text protocol + tk1.MustExec("prepare s from 'select /*+use_index(t1, ic)*/ * from t1 where 1'") + // prepare binary protocol + stmtID, _, _, err := tk2.Se.PrepareStmt("select /*+use_index(t1, ic)*/ * from t1 where 1") + c.Assert(err, IsNil) + tk1.MustExec("set tx_isolation='READ-COMMITTED'") + tk1.MustExec("begin pessimistic") + tk2.MustExec("set tx_isolation='READ-COMMITTED'") + tk2.MustExec("begin pessimistic") + tk1.MustQuery("execute s").Check(testkit.Rows()) + rs, err := tk2.Se.ExecutePreparedStmt(ctx, stmtID, []types.Datum{}) + c.Assert(err, IsNil) + tk2.ResultSetToResult(rs, Commentf("%v", rs)).Check(testkit.Rows()) + + tk3 := testkit.NewTestKit(c, store) + tk3.MustExec("use test") + tk3.MustExec("alter table t1 drop index ic") + tk3.MustExec("insert into t1 values(1, 0)") + + // The execution after schema changed should not hit plan cache. + // execute text protocol + tk1.MustQuery("execute s").Check(testkit.Rows("1 0")) + tk1.MustQuery("select @@last_plan_from_cache").Check(testkit.Rows("0")) + // execute binary protocol + rs, err = tk2.Se.ExecutePreparedStmt(ctx, stmtID, []types.Datum{}) + c.Assert(err, IsNil) + tk2.ResultSetToResult(rs, Commentf("%v", rs)).Check(testkit.Rows("1 0")) + tk2.MustQuery("select @@last_plan_from_cache").Check(testkit.Rows("0")) +} diff --git a/planner/core/rule_build_key_info.go b/planner/core/rule_build_key_info.go index ae8a0a1d3a566..41e8b195af45a 100644 --- a/planner/core/rule_build_key_info.go +++ b/planner/core/rule_build_key_info.go @@ -267,8 +267,10 @@ func (ds *DataSource) BuildKeyInfo(selfSchema *expression.Schema, childSchema [] var latestIndexes map[int64]*model.IndexInfo var changed bool var err error + check := ds.ctx.GetSessionVars().IsIsolation(ast.ReadCommitted) || ds.isForUpdateRead + check = check && ds.ctx.GetSessionVars().ConnectionID > 0 // we should check index valid while forUpdateRead, see detail in https://github.com/pingcap/tidb/pull/22152 - if ds.isForUpdateRead { + if check { latestIndexes, changed, err = getLatestIndexInfo(ds.ctx, ds.table.Meta().ID, 0) if err != nil { return diff --git a/planner/optimize.go b/planner/optimize.go index 73adf7ee9e480..842e567633e21 100644 --- a/planner/optimize.go +++ b/planner/optimize.go @@ -71,7 +71,12 @@ func GetExecuteForUpdateReadIS(node ast.Node, sctx sessionctx.Context) infoschem execID = vars.PreparedStmtNameToID[execStmt.Name] } if preparedPointer, ok := vars.PreparedStmts[execID]; ok { - if preparedObj, ok := preparedPointer.(*core.CachedPrepareStmt); ok && preparedObj.ForUpdateRead { + checkSchema := vars.IsIsolation(ast.ReadCommitted) + if !checkSchema { + preparedObj, ok := preparedPointer.(*core.CachedPrepareStmt) + checkSchema = ok && preparedObj.ForUpdateRead + } + if checkSchema { is := domain.GetDomain(sctx).InfoSchema() return temptable.AttachLocalTemporaryTableInfoSchema(sctx, is) } diff --git a/session/session.go b/session/session.go index 5a1c2ad164c62..c975cd7a29f32 100644 --- a/session/session.go +++ b/session/session.go @@ -2140,6 +2140,9 @@ func (s *session) ExecutePreparedStmt(ctx context.Context, stmtID uint32, args [ if err != nil { return nil, errors.Trace(err) } + } else if s.sessionVars.IsIsolation(ast.ReadCommitted) || preparedStmt.ForUpdateRead { + is = domain.GetDomain(s).InfoSchema() + is = temptable.AttachLocalTemporaryTableInfoSchema(s, is) } else { is = s.GetInfoSchema().(infoschema.InfoSchema) }