1515package priorityqueue
1616
1717import (
18+ "fmt"
1819 "strings"
20+ "time"
1921
2022 "github.com/pingcap/tidb/pkg/sessionctx"
2123 "github.com/pingcap/tidb/pkg/sessionctx/variable"
2224 "github.com/pingcap/tidb/pkg/statistics/handle/autoanalyze/exec"
25+ statslogutil "github.com/pingcap/tidb/pkg/statistics/handle/logutil"
2326 statstypes "github.com/pingcap/tidb/pkg/statistics/handle/types"
2427 statsutil "github.com/pingcap/tidb/pkg/statistics/handle/util"
28+ "go.uber.org/zap"
2529)
2630
31+ // defaultFailedAnalysisWaitTime is the default wait time for the next analysis after a failed analysis.
32+ // NOTE: this is only used when the average analysis duration is not available.(No successful analysis before)
33+ const defaultFailedAnalysisWaitTime = 30 * time .Minute
34+
2735// TableAnalysisJob defines the structure for table analysis job information.
36+ // TODO: add stringer for TableAnalysisJob.
2837type TableAnalysisJob struct {
2938 // Only set when partitions's indexes need to be analyzed.
3039 PartitionIndexes map [string ][]string
@@ -40,6 +49,128 @@ type TableAnalysisJob struct {
4049 Weight float64
4150}
4251
52+ // IsValidToAnalyze checks whether the table is valid to analyze.
53+ // It checks the last failed analysis duration and the average analysis duration.
54+ // If the last failed analysis duration is less than 2 times the average analysis duration,
55+ // we skip this table to avoid too much failed analysis.
56+ func (j * TableAnalysisJob ) IsValidToAnalyze (
57+ sctx sessionctx.Context ,
58+ ) (bool , string ) {
59+ // No need to analyze this table.
60+ // TODO: Usually, we should not put this kind of table into the queue.
61+ if j .Weight == 0 {
62+ return false , "weight is 0"
63+ }
64+
65+ // Check whether the table or partition is valid to analyze.
66+ if len (j .Partitions ) > 0 || len (j .PartitionIndexes ) > 0 {
67+ // Any partition is invalid to analyze, the whole table is invalid to analyze.
68+ // Because we need to analyze partitions in batch mode.
69+ partitions := append (j .Partitions , getPartitionNames (j .PartitionIndexes )... )
70+ if valid , failReason := isValidToAnalyze (
71+ sctx ,
72+ j .TableSchema ,
73+ j .TableName ,
74+ partitions ... ,
75+ ); ! valid {
76+ return false , failReason
77+ }
78+ } else {
79+ if valid , failReason := isValidToAnalyze (
80+ sctx ,
81+ j .TableSchema ,
82+ j .TableName ,
83+ ); ! valid {
84+ return false , failReason
85+ }
86+ }
87+
88+ return true , ""
89+ }
90+
91+ func getPartitionNames (partitionIndexes map [string ][]string ) []string {
92+ names := make ([]string , 0 , len (partitionIndexes ))
93+ for _ , partitionNames := range partitionIndexes {
94+ names = append (names , partitionNames ... )
95+ }
96+ return names
97+ }
98+
99+ func isValidToAnalyze (
100+ sctx sessionctx.Context ,
101+ schema , table string ,
102+ partitionNames ... string ,
103+ ) (bool , string ) {
104+ lastFailedAnalysisDuration , err :=
105+ getLastFailedAnalysisDuration (sctx , schema , table , partitionNames ... )
106+ if err != nil {
107+ statslogutil .StatsLogger ().Warn (
108+ "Fail to get last failed analysis duration" ,
109+ zap .String ("schema" , schema ),
110+ zap .String ("table" , table ),
111+ zap .Strings ("partitions" , partitionNames ),
112+ zap .Error (err ),
113+ )
114+ return false , fmt .Sprintf ("fail to get last failed analysis duration: %v" , err )
115+ }
116+
117+ averageAnalysisDuration , err :=
118+ getAverageAnalysisDuration (sctx , schema , table , partitionNames ... )
119+ if err != nil {
120+ statslogutil .StatsLogger ().Warn (
121+ "Fail to get average analysis duration" ,
122+ zap .String ("schema" , schema ),
123+ zap .String ("table" , table ),
124+ zap .Strings ("partitions" , partitionNames ),
125+ zap .Error (err ),
126+ )
127+ return false , fmt .Sprintf ("fail to get average analysis duration: %v" , err )
128+ }
129+
130+ // Last analysis just failed, we should not analyze it again.
131+ if lastFailedAnalysisDuration == justFailed {
132+ // The last analysis failed, we should not analyze it again.
133+ statslogutil .StatsLogger ().Info (
134+ "Skip analysis because the last analysis just failed" ,
135+ zap .String ("schema" , schema ),
136+ zap .String ("table" , table ),
137+ zap .Strings ("partitions" , partitionNames ),
138+ )
139+ return false , "last analysis just failed"
140+ }
141+
142+ // Failed analysis duration is less than 2 times the average analysis duration.
143+ // Skip this table to avoid too much failed analysis.
144+ onlyFailedAnalysis := lastFailedAnalysisDuration != noRecord && averageAnalysisDuration == noRecord
145+ if onlyFailedAnalysis && lastFailedAnalysisDuration < defaultFailedAnalysisWaitTime {
146+ statslogutil .StatsLogger ().Info (
147+ fmt .Sprintf ("Skip analysis because the last failed analysis duration is less than %v" , defaultFailedAnalysisWaitTime ),
148+ zap .String ("schema" , schema ),
149+ zap .String ("table" , table ),
150+ zap .Strings ("partitions" , partitionNames ),
151+ zap .Duration ("lastFailedAnalysisDuration" , lastFailedAnalysisDuration ),
152+ zap .Duration ("averageAnalysisDuration" , averageAnalysisDuration ),
153+ )
154+ return false , fmt .Sprintf ("last failed analysis duration is less than %v" , defaultFailedAnalysisWaitTime )
155+ }
156+ // Failed analysis duration is less than 2 times the average analysis duration.
157+ meetSkipCondition := lastFailedAnalysisDuration != noRecord &&
158+ lastFailedAnalysisDuration < 2 * averageAnalysisDuration
159+ if meetSkipCondition {
160+ statslogutil .StatsLogger ().Info (
161+ "Skip analysis because the last failed analysis duration is less than 2 times the average analysis duration" ,
162+ zap .String ("schema" , schema ),
163+ zap .String ("table" , table ),
164+ zap .Strings ("partitions" , partitionNames ),
165+ zap .Duration ("lastFailedAnalysisDuration" , lastFailedAnalysisDuration ),
166+ zap .Duration ("averageAnalysisDuration" , averageAnalysisDuration ),
167+ )
168+ return false , "last failed analysis duration is less than 2 times the average analysis duration"
169+ }
170+
171+ return true , ""
172+ }
173+
43174// Execute executes the analyze statement.
44175func (j * TableAnalysisJob ) Execute (
45176 statsHandle statstypes.StatsHandle ,
0 commit comments