Repository: pingcap/tidb (Go)

Issue:
Do not allow set `tidb_auto_analyze_ratio` to 0
## Enhancement

Currently, if you set [tidb_auto_analyze_ratio](https://docs.pingcap.com/tidb/stable/system-variables#tidb_auto_analyze_ratio) to 0, we will disable auto-analysis based on row changes, but we will still process the newly added indexes and tables.

This behavior is difficult to understand. I suppose most users will think 0 means we always need to analyze the table, no matter how many rows have been updated.

The historic reason we introduced it is that before https://github.com/pingcap/tidb/pull/34643 this PR, there was no way to disable the auto-analyze feature without restarting the server. So we chose this special value to allow users to disable the auto-analysis feature. But now enable_tidb_auto_analyze is a global session variable. So we don't depend on this value.

So Roger and I suggest getting rid of this old behavior. No longer allow users to set it to zero.

However, I am not sure how to deal with the existing users' settings.

I think we have the following choices:
1. Help users to set `enable_tidb_auto_analyze` to off if the user's `tidb_anto_analyze_ratio` is 0.
2. Keep the old implementation and also keep the user's settings during the upgrade process. But we don't allow changing this value to 0 anymore.




Discussion: options 2 looks fine to me. 

I believe the case setting `tidb_anto_analyze_ratio` to 0 is rare.  

In addition, we can add extra comment in user doc of `tidb_anto_analyze_ratio`. If users want to completely disable auto analyze, go for `enable_tidb_auto_analyze` instead of setting `tidb_anto_analyze_ratio` to 0. 
I am working on this.



Fix (patch):
diff --git a/pkg/sessionctx/variable/sysvar.go b/pkg/sessionctx/variable/sysvar.go
index 190a275b8ff0c..6e1918ab1d2c3 100644
--- a/pkg/sessionctx/variable/sysvar.go
+++ b/pkg/sessionctx/variable/sysvar.go
@@ -723,7 +723,27 @@ var defaultSysVars = []*SysVar{
 		EnableLocalTxn.Store(newVal)
 		return nil
 	}},
-	{Scope: ScopeGlobal, Name: TiDBAutoAnalyzeRatio, Value: strconv.FormatFloat(DefAutoAnalyzeRatio, 'f', -1, 64), Type: TypeFloat, MinValue: 0, MaxValue: math.MaxUint64},
+	{
+		Scope:    ScopeGlobal,
+		Name:     TiDBAutoAnalyzeRatio,
+		Value:    strconv.FormatFloat(DefAutoAnalyzeRatio, 'f', -1, 64),
+		Type:     TypeFloat,
+		MinValue: 0,
+		MaxValue: math.MaxUint64,
+		// The value of TiDBAutoAnalyzeRatio should be greater than 0.00001 or equal to 0.00001.
+		Validation: func(vars *SessionVars, normalizedValue string, originalValue string, scope ScopeFlag) (string, error) {
+			ratio, err := strconv.ParseFloat(normalizedValue, 64)
+			if err != nil {
+				return "", err
+			}
+			const minRatio = 0.00001
+			const tolerance = 1e-9
+			if ratio < minRatio && math.Abs(ratio-minRatio) > tolerance {
+				return "", errors.Errorf("the value of %s should be greater than or equal to %f", TiDBAutoAnalyzeRatio, minRatio)
+			}
+			return normalizedValue, nil
+		},
+	},
 	{Scope: ScopeGlobal, Name: TiDBAutoAnalyzeStartTime, Value: DefAutoAnalyzeStartTime, Type: TypeTime},
 	{Scope: ScopeGlobal, Name: TiDBAutoAnalyzeEndTime, Value: DefAutoAnalyzeEndTime, Type: TypeTime},
 	{Scope: ScopeGlobal, Name: TiDBMemQuotaBindingCache, Value: strconv.FormatInt(DefTiDBMemQuotaBindingCache, 10), Type: TypeUnsigned, MaxValue: math.MaxInt32, GetGlobal: func(_ context.Context, sv *SessionVars) (string, error) {