From 122ee4d8fc9cf1de007960d1c7f43781ac43d9e7 Mon Sep 17 00:00:00 2001 From: ti-srebot <66930949+ti-srebot@users.noreply.github.com> Date: Thu, 11 Mar 2021 15:22:55 +0800 Subject: [PATCH] statistics: fix a case that auto-analyze is triggered outside its time range (#23214) (#23219) --- statistics/handle/update.go | 19 +++++++---- statistics/handle/update_test.go | 55 ++++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+), 6 deletions(-) diff --git a/statistics/handle/update.go b/statistics/handle/update.go index affd2c0ca2457..a17357cef2c5b 100644 --- a/statistics/handle/update.go +++ b/statistics/handle/update.go @@ -671,6 +671,11 @@ func TableAnalyzed(tbl *statistics.Table) bool { // "tbl.ModifyCount/tbl.Count > autoAnalyzeRatio" and the current time is // between `start` and `end`. func NeedAnalyzeTable(tbl *statistics.Table, limit time.Duration, autoAnalyzeRatio float64, start, end, now time.Time) (bool, string) { + // Tests if current time is within the time period. + if !timeutil.WithinDayTimePeriod(start, end, now) { + return false, "" + } + analyzed := TableAnalyzed(tbl) if !analyzed { t := time.Unix(0, oracle.ExtractPhysical(tbl.Version)*int64(time.Millisecond)) @@ -685,8 +690,7 @@ func NeedAnalyzeTable(tbl *statistics.Table, limit time.Duration, autoAnalyzeRat if float64(tbl.ModifyCount)/float64(tbl.Count) <= autoAnalyzeRatio { return false, "" } - // Tests if current time is within the time period. - return timeutil.WithinDayTimePeriod(start, end, now), fmt.Sprintf("too many modifications(%v/%v>%v)", tbl.ModifyCount, tbl.Count, autoAnalyzeRatio) + return true, fmt.Sprintf("too many modifications(%v/%v>%v)", tbl.ModifyCount, tbl.Count, autoAnalyzeRatio) } func (h *Handle) getAutoAnalyzeParameters() map[string]string { @@ -727,14 +731,14 @@ func parseAnalyzePeriod(start, end string) (time.Time, time.Time, error) { } // HandleAutoAnalyze analyzes the newly created table or index. -func (h *Handle) HandleAutoAnalyze(is infoschema.InfoSchema) { +func (h *Handle) HandleAutoAnalyze(is infoschema.InfoSchema) (analyzed bool) { dbs := is.AllSchemaNames() parameters := h.getAutoAnalyzeParameters() autoAnalyzeRatio := parseAutoAnalyzeRatio(parameters[variable.TiDBAutoAnalyzeRatio]) start, end, err := parseAnalyzePeriod(parameters[variable.TiDBAutoAnalyzeStartTime], parameters[variable.TiDBAutoAnalyzeEndTime]) if err != nil { logutil.BgLogger().Error("[stats] parse auto analyze period failed", zap.Error(err)) - return + return false } for _, db := range dbs { tbls := is.SchemaTables(model.NewCIStr(db)) @@ -747,7 +751,9 @@ func (h *Handle) HandleAutoAnalyze(is infoschema.InfoSchema) { sql := fmt.Sprintf("analyze table %s", tblName) analyzed := h.autoAnalyzeTable(tblInfo, statsTbl, start, end, autoAnalyzeRatio, sql) if analyzed { - return + // analyze one table at a time to let it get the freshest parameters. + // others will be analyzed next round which is just 3s later. + return true } continue } @@ -756,12 +762,13 @@ func (h *Handle) HandleAutoAnalyze(is infoschema.InfoSchema) { statsTbl := h.GetPartitionStats(tblInfo, def.ID) analyzed := h.autoAnalyzeTable(tblInfo, statsTbl, start, end, autoAnalyzeRatio, sql) if analyzed { - return + return true } continue } } } + return false } func (h *Handle) autoAnalyzeTable(tblInfo *model.TableInfo, statsTbl *statistics.Table, start, end time.Time, ratio float64, sql string) bool { diff --git a/statistics/handle/update_test.go b/statistics/handle/update_test.go index 06196cb9f226f..712dc77cf3ca4 100644 --- a/statistics/handle/update_test.go +++ b/statistics/handle/update_test.go @@ -41,6 +41,26 @@ import ( ) var _ = Suite(&testStatsSuite{}) +var _ = SerialSuites(&testSerialStatsSuite{}) + +type testSerialStatsSuite struct { + store kv.Storage + do *domain.Domain +} + +func (s *testSerialStatsSuite) SetUpSuite(c *C) { + testleak.BeforeTest() + // Add the hook here to avoid data race. + var err error + s.store, s.do, err = newStoreWithBootstrap() + c.Assert(err, IsNil) +} + +func (s *testSerialStatsSuite) TearDownSuite(c *C) { + s.do.Close() + s.store.Close() + testleak.AfterTest(c)() +} type testStatsSuite struct { store kv.Storage @@ -465,6 +485,41 @@ func (s *testStatsSuite) TestAutoUpdate(c *C) { c.Assert(hg.Len(), Equals, 3) } +func (s *testSerialStatsSuite) TestAutoAnalyzeOnEmptyTable(c *C) { + defer cleanEnv(c, s.store, s.do) + tk := testkit.NewTestKit(c, s.store) + + oriStart := tk.MustQuery("select @@tidb_auto_analyze_start_time").Rows()[0][0].(string) + oriEnd := tk.MustQuery("select @@tidb_auto_analyze_end_time").Rows()[0][0].(string) + defer func() { + tk.MustExec(fmt.Sprintf("set global tidb_auto_analyze_start_time='%v'", oriStart)) + tk.MustExec(fmt.Sprintf("set global tidb_auto_analyze_end_time='%v'", oriEnd)) + }() + + t := time.Now().Add(-1 * time.Minute) + h, m := t.Hour(), t.Minute() + start, end := fmt.Sprintf("%02d:%02d +0000", h, m), fmt.Sprintf("%02d:%02d +0000", h, m) + tk.MustExec(fmt.Sprintf("set global tidb_auto_analyze_start_time='%v'", start)) + tk.MustExec(fmt.Sprintf("set global tidb_auto_analyze_end_time='%v'", end)) + s.do.StatsHandle().HandleAutoAnalyze(s.do.InfoSchema()) + + tk.MustExec("use test") + tk.MustExec("create table t (a int, index idx(a))") + // to pass the stats.Pseudo check in autoAnalyzeTable + tk.MustExec("analyze table t") + // to pass the AutoAnalyzeMinCnt check in autoAnalyzeTable + tk.MustExec("insert into t values (1)" + strings.Repeat(", (1)", int(handle.AutoAnalyzeMinCnt))) + c.Assert(s.do.StatsHandle().DumpStatsDeltaToKV(handle.DumpAll), IsNil) + c.Assert(s.do.StatsHandle().Update(s.do.InfoSchema()), IsNil) + + // test if it will be limited by the time range + c.Assert(s.do.StatsHandle().HandleAutoAnalyze(s.do.InfoSchema()), IsFalse) + + tk.MustExec(fmt.Sprintf("set global tidb_auto_analyze_start_time='00:00 +0000'")) + tk.MustExec(fmt.Sprintf("set global tidb_auto_analyze_end_time='23:59 +0000'")) + c.Assert(s.do.StatsHandle().HandleAutoAnalyze(s.do.InfoSchema()), IsTrue) +} + func (s *testStatsSuite) TestAutoUpdatePartition(c *C) { defer cleanEnv(c, s.store, s.do) testKit := testkit.NewTestKit(c, s.store)