From 0b8f8807d0335b7eb62a6951815d6567d345e3a9 Mon Sep 17 00:00:00 2001 From: Zhou Kunqin <25057648+time-and-fate@users.noreply.github.com> Date: Tue, 25 Apr 2023 18:31:54 +0800 Subject: [PATCH] *: add a new variable to control whether to invalidate plan cache when related table analyzed (#43390) close pingcap/tidb#43295 --- planner/core/plan_cache_lru.go | 4 ++- planner/core/plan_cache_test.go | 6 +++- session/bootstrap.go | 25 ++++++++++++++++- session/bootstrap_test.go | 47 ++++++++++++++++++++++++++++++++ sessionctx/variable/session.go | 4 +++ sessionctx/variable/sysvar.go | 4 +++ sessionctx/variable/tidb_vars.go | 4 +++ 7 files changed, 91 insertions(+), 3 deletions(-) diff --git a/planner/core/plan_cache_lru.go b/planner/core/plan_cache_lru.go index 2fb1a60d4b4d5..152acdb2fbfad 100644 --- a/planner/core/plan_cache_lru.go +++ b/planner/core/plan_cache_lru.go @@ -274,7 +274,9 @@ func (l *LRUPlanCache) pickFromBucket(bucket map[*list.Element]struct{}, matchOp continue } // table stats has changed - if plan.matchOpts.StatsVersionHash != matchOpts.StatsVersionHash { + // this check can be disabled by turning off system variable tidb_plan_cache_invalidation_on_fresh_stats + if l.sctx.GetSessionVars().PlanCacheInvalidationOnFreshStats && + plan.matchOpts.StatsVersionHash != matchOpts.StatsVersionHash { continue } diff --git a/planner/core/plan_cache_test.go b/planner/core/plan_cache_test.go index f4942cb949351..79329560c6958 100644 --- a/planner/core/plan_cache_test.go +++ b/planner/core/plan_cache_test.go @@ -559,7 +559,11 @@ func TestPreparedPlanCacheStats(t *testing.T) { tk.MustExec(`execute st using @a`) tk.MustQuery(`select @@last_plan_from_cache`).Check(testkit.Rows("1")) tk.MustExec("analyze table t") - tk.MustExec(`execute st using @a`) // stats changes can affect prep cache hit + tk.MustExec("set tidb_plan_cache_invalidation_on_fresh_stats = 0") + tk.MustExec(`execute st using @a`) + tk.MustQuery(`select @@last_plan_from_cache`).Check(testkit.Rows("1")) + tk.MustExec("set tidb_plan_cache_invalidation_on_fresh_stats = 1") + tk.MustExec(`execute st using @a`) // stats changes can affect prep cache hit if we turn on the variable tk.MustQuery(`select @@last_plan_from_cache`).Check(testkit.Rows("0")) tk.MustExec(`execute st using @a`) tk.MustQuery(`select @@last_plan_from_cache`).Check(testkit.Rows("1")) diff --git a/session/bootstrap.go b/session/bootstrap.go index fb410496c16fd..454719f0241c2 100644 --- a/session/bootstrap.go +++ b/session/bootstrap.go @@ -865,11 +865,14 @@ const ( version142 = 142 // version 143 add column `error` to `mysql.tidb_global_task` and `mysql.tidb_background_subtask` version143 = 143 + // version 144 turn off `tidb_plan_cache_invalidation_on_fresh_stats`, which is introduced in 7.1-rc, + // if it's upgraded from an existing old version cluster. + version144 = 144 ) // currentBootstrapVersion is defined as a variable, so we can modify its value for testing. // please make sure this is the largest version -var currentBootstrapVersion int64 = version143 +var currentBootstrapVersion int64 = version144 // DDL owner key's expired time is ManagerSessionTTL seconds, we should wait the time and give more time to have a chance to finish it. var internalSQLTimeout = owner.ManagerSessionTTL + 15 @@ -1001,6 +1004,7 @@ var ( upgradeToVer141, upgradeToVer142, upgradeToVer143, + upgradeToVer144, } ) @@ -2506,6 +2510,25 @@ func upgradeToVer143(s Session, ver int64) { doReentrantDDL(s, "ALTER TABLE mysql.tidb_background_subtask ADD COLUMN `error` BLOB", infoschema.ErrColumnExists) } +func upgradeToVer144(s Session, ver int64) { + if ver >= version144 { + return + } + ctx := kv.WithInternalSourceType(context.Background(), kv.InternalTxnBootstrap) + rs, err := s.ExecuteInternal(ctx, "SELECT VARIABLE_VALUE FROM %n.%n WHERE VARIABLE_NAME=%?;", + mysql.SystemDB, mysql.GlobalVariablesTable, variable.TiDBPlanCacheInvalidationOnFreshStats) + terror.MustNil(err) + req := rs.NewChunk(nil) + err = rs.Next(ctx, req) + terror.MustNil(err) + if req.NumRows() != 0 { + return + } + + mustExecute(s, "INSERT HIGH_PRIORITY IGNORE INTO %n.%n VALUES (%?, %?);", + mysql.SystemDB, mysql.GlobalVariablesTable, variable.TiDBPlanCacheInvalidationOnFreshStats, variable.Off) +} + func writeOOMAction(s Session) { comment := "oom-action is `log` by default in v3.0.x, `cancel` by default in v4.0.11+" mustExecute(s, `INSERT HIGH_PRIORITY INTO %n.%n VALUES (%?, %?, %?) ON DUPLICATE KEY UPDATE VARIABLE_VALUE= %?`, diff --git a/session/bootstrap_test.go b/session/bootstrap_test.go index 31a07c850ec29..fd208a46e03d9 100644 --- a/session/bootstrap_test.go +++ b/session/bootstrap_test.go @@ -2131,3 +2131,50 @@ func TestTiDBLoadBasedReplicaReadThresholdUpgradingToVer141(t *testing.T) { require.Equal(t, 2, row.Len()) require.Equal(t, "1s", row.GetString(1)) } + +func TestTiDBPlanCacheInvalidationOnFreshStatsWhenUpgradingToVer144(t *testing.T) { + ctx := context.Background() + store, _ := CreateStoreAndBootstrap(t) + defer func() { require.NoError(t, store.Close()) }() + + // bootstrap as version143 + ver143 := version143 + seV143 := CreateSessionAndSetID(t, store) + txn, err := store.Begin() + require.NoError(t, err) + m := meta.NewMeta(txn) + err = m.FinishBootstrap(int64(ver143)) + require.NoError(t, err) + mustExec(t, seV143, fmt.Sprintf("update mysql.tidb set variable_value=%d where variable_name='tidb_server_version'", ver143)) + // simulate a real ver143 where `tidb_plan_cache_invalidation_on_fresh_stats` doesn't exist yet + mustExec(t, seV143, "delete from mysql.GLOBAL_VARIABLES where variable_name='tidb_plan_cache_invalidation_on_fresh_stats'") + err = txn.Commit(context.Background()) + require.NoError(t, err) + unsetStoreBootstrapped(store.UUID()) + + // upgrade to ver144 + domCurVer, err := BootstrapSession(store) + require.NoError(t, err) + defer domCurVer.Close() + seCurVer := CreateSessionAndSetID(t, store) + ver, err := getBootstrapVersion(seCurVer) + require.NoError(t, err) + require.Equal(t, currentBootstrapVersion, ver) + + // the value in the table is set to OFF automatically + res := mustExecToRecodeSet(t, seCurVer, "select * from mysql.GLOBAL_VARIABLES where variable_name='tidb_plan_cache_invalidation_on_fresh_stats'") + chk := res.NewChunk(nil) + require.NoError(t, res.Next(ctx, chk)) + require.Equal(t, 1, chk.NumRows()) + row := chk.GetRow(0) + require.Equal(t, "OFF", row.GetString(1)) + + // the session and global variable is also OFF + res = mustExecToRecodeSet(t, seCurVer, "select @@session.tidb_plan_cache_invalidation_on_fresh_stats, @@global.tidb_plan_cache_invalidation_on_fresh_stats") + chk = res.NewChunk(nil) + require.NoError(t, res.Next(ctx, chk)) + require.Equal(t, 1, chk.NumRows()) + row = chk.GetRow(0) + require.Equal(t, int64(0), row.GetInt64(0)) + require.Equal(t, int64(0), row.GetInt64(1)) +} diff --git a/sessionctx/variable/session.go b/sessionctx/variable/session.go index 9bcc8058023d0..7e75202ee5a4d 100644 --- a/sessionctx/variable/session.go +++ b/sessionctx/variable/session.go @@ -1315,6 +1315,10 @@ type SessionVars struct { // EnableNonPreparedPlanCacheForDML indicates whether to enable non-prepared plan cache for DML statements. EnableNonPreparedPlanCacheForDML bool + // PlanCacheInvalidationOnFreshStats controls if plan cache will be invalidated automatically when + // related stats are analyzed after the plan cache is generated. + PlanCacheInvalidationOnFreshStats bool + // NonPreparedPlanCacheSize controls the size of non-prepared plan cache. NonPreparedPlanCacheSize uint64 diff --git a/sessionctx/variable/sysvar.go b/sessionctx/variable/sysvar.go index 5be3dd301c82c..0492be84acee9 100644 --- a/sessionctx/variable/sysvar.go +++ b/sessionctx/variable/sysvar.go @@ -2505,6 +2505,10 @@ var defaultSysVars = []*SysVar{ s.OptimizerFixControl = newMap return nil }}, + {Scope: ScopeGlobal | ScopeSession, Name: TiDBPlanCacheInvalidationOnFreshStats, Value: BoolToOnOff(DefTiDBPlanCacheInvalidationOnFreshStats), Type: TypeBool, SetSession: func(s *SessionVars, val string) error { + s.PlanCacheInvalidationOnFreshStats = TiDBOptOn(val) + return nil + }}, } func setTiFlashComputeDispatchPolicy(s *SessionVars, val string) error { diff --git a/sessionctx/variable/tidb_vars.go b/sessionctx/variable/tidb_vars.go index f620814255361..63d74e62cc209 100644 --- a/sessionctx/variable/tidb_vars.go +++ b/sessionctx/variable/tidb_vars.go @@ -787,6 +787,9 @@ const ( TiDBNonPreparedPlanCacheSize = "tidb_non_prepared_plan_cache_size" // TiDBPlanCacheMaxPlanSize controls the maximum size of a plan that can be cached. TiDBPlanCacheMaxPlanSize = "tidb_plan_cache_max_plan_size" + // TiDBPlanCacheInvalidationOnFreshStats controls if plan cache will be invalidated automatically when + // related stats are analyzed after the plan cache is generated. + TiDBPlanCacheInvalidationOnFreshStats = "tidb_plan_cache_invalidation_on_fresh_stats" // TiDBSessionPlanCacheSize controls the size of session plan cache. TiDBSessionPlanCacheSize = "tidb_session_plan_cache_size" @@ -1260,6 +1263,7 @@ const ( DefTiDBLoadBasedReplicaReadThreshold = time.Second DefTiDBOptEnableLateMaterialization = true DefTiDBOptOrderingIdxSelThresh = 0.0 + DefTiDBPlanCacheInvalidationOnFreshStats = true ) // Process global variables.