@@ -37,6 +37,7 @@ import (
3737 "github.com/pingcap/tidb/pkg/sessionctx"
3838 "github.com/pingcap/tidb/pkg/sessionctx/stmtctx"
3939 "github.com/pingcap/tidb/pkg/statistics"
40+ "github.com/pingcap/tidb/pkg/statistics/asyncload"
4041 "github.com/pingcap/tidb/pkg/statistics/handle/ddl/testutil"
4142 "github.com/pingcap/tidb/pkg/statistics/handle/types"
4243 "github.com/pingcap/tidb/pkg/testkit"
@@ -404,6 +405,85 @@ func TestPlanStatsLoadTimeout(t *testing.T) {
404405 })
405406}
406407
408+ func TestPreparedPlanCacheInvalidatedAfterSyncLoadTimeoutFallback (t * testing.T ) {
409+ originConfig := config .GetGlobalConfig ()
410+ newConfig := config .NewConfig ()
411+ newConfig .Performance .StatsLoadConcurrency = - 1 // no worker to consume channel
412+ newConfig .Performance .StatsLoadQueueSize = 1
413+ config .StoreGlobalConfig (newConfig )
414+ t .Cleanup (func () {
415+ config .StoreGlobalConfig (originConfig )
416+ })
417+
418+ store , dom := testkit .CreateMockStoreAndDomain (t )
419+ tk := testkit .NewTestKit (t , store )
420+ tk .MustExec ("use test" )
421+ originalPseudoTimeout := tk .MustQuery ("select @@tidb_stats_load_pseudo_timeout" ).Rows ()[0 ][0 ].(string )
422+ defer func () {
423+ tk .MustExec (fmt .Sprintf ("set global tidb_stats_load_pseudo_timeout = %v" , originalPseudoTimeout ))
424+ }()
425+
426+ oriLease := dom .StatsHandle ().Lease ()
427+ dom .StatsHandle ().SetLease (1 )
428+ defer func () {
429+ dom .StatsHandle ().SetLease (oriLease )
430+ }()
431+
432+ tk .MustExec ("set global tidb_stats_load_pseudo_timeout = 1" )
433+ tk .MustExec ("set @@session.tidb_enable_prepared_plan_cache = 1" )
434+ tk .MustExec ("set @@session.tidb_plan_cache_invalidation_on_fresh_stats = 1" )
435+ tk .MustExec ("set @@session.tidb_stats_load_sync_wait = 1" )
436+ tk .MustExec ("drop table if exists t" )
437+ tk .MustExec ("create table t(a int primary key, b int)" )
438+ tk .MustExec ("insert into t values (1,1),(2,2),(3,3),(4,4),(5,5)" )
439+ tk .MustExec ("analyze table t all columns" )
440+ require .NoError (t , dom .StatsHandle ().Update (context .Background (), dom .InfoSchema ()))
441+
442+ tbl , err := dom .InfoSchema ().TableByName (context .Background (), ast .NewCIStr ("test" ), ast .NewCIStr ("t" ))
443+ require .NoError (t , err )
444+ tblInfo := tbl .Meta ()
445+ colBID := tblInfo .Columns [1 ].ID
446+
447+ statsTbl := dom .StatsHandle ().GetPhysicalTableStats (tblInfo .ID , tblInfo )
448+ colBStats := statsTbl .GetCol (colBID )
449+ require .NotNil (t , colBStats )
450+ require .True (t , colBStats .IsAllEvicted ())
451+
452+ tk .MustExec ("prepare st from 'select * from t where b > ?'" )
453+ tk .MustExec ("set @p = 2" )
454+ tk .MustExec ("execute st using @p" )
455+ tk .MustQuery ("select @@last_plan_from_cache" ).Check (testkit .Rows ("0" ))
456+
457+ // Wait for the async histogram load item corresponding to column b to be marked
458+ // as sync-load failed, which confirms the sync-load path timed out and fell back
459+ // to async loading for this full-load stats item.
460+ require .Eventually (t , func () bool {
461+ for _ , item := range asyncload .AsyncLoadHistogramNeededItems .AllItems () {
462+ if item .TableID == tblInfo .ID && item .ID == colBID && ! item .IsIndex && item .FullLoad && item .IsSyncLoadFailed {
463+ return true
464+ }
465+ }
466+ return false
467+ }, 5 * time .Second , 100 * time .Millisecond )
468+
469+ tk .MustExec ("execute st using @p" )
470+ tk .MustQuery ("select @@last_plan_from_cache" ).Check (testkit .Rows ("0" ))
471+ require .Equal (t , 0 , tk .Session ().GetSessionPlanCache ().Size ())
472+
473+ require .NoError (t , dom .StatsHandle ().LoadNeededHistograms (dom .InfoSchema ()))
474+ statsTbl = dom .StatsHandle ().GetPhysicalTableStats (tblInfo .ID , tblInfo )
475+ colBStats = statsTbl .GetCol (colBID )
476+ require .NotNil (t , colBStats )
477+ require .True (t , colBStats .IsFullLoad ())
478+
479+ tk .MustExec ("execute st using @p" )
480+ tk .MustQuery ("select @@last_plan_from_cache" ).Check (testkit .Rows ("0" ))
481+ require .Equal (t , 1 , tk .Session ().GetSessionPlanCache ().Size ())
482+
483+ tk .MustExec ("execute st using @p" )
484+ tk .MustQuery ("select @@last_plan_from_cache" ).Check (testkit .Rows ("1" ))
485+ }
486+
407487func TestPlanStatsStatusRecord (t * testing.T ) {
408488 defer config .RestoreFunc ()()
409489 config .UpdateGlobal (func (conf * config.Config ) {
0 commit comments