Repository: pingcap/tidb
File: pkg/statistics/histogram.go (Go)

Code before:
	// LessCounts are equal only if no valid buckets or both values are out of range
	isInValidBucket := lessCountA != lessCountB
	// If values in the same bucket, use skewRatio to adjust the range estimate to account for potential skew.
	if isInValidBucket && bktIndexA == bktIndexB {
		// sctx may be nil for stats version 1
		if sctx != nil {
			skewRatio := sctx.GetSessionVars().RiskRangeSkewRatio
			sctx.GetSessionVars().RecordRelevantOptVar(vardef.TiDBOptRiskRangeSkewRatio)
			// Worst case skew is if the range includes all the rows in the bucket
			skewEstimate := hg.Buckets[bktIndexA].Count
			if bktIndexA > 0 {
				skewEstimate -= hg.Buckets[bktIndexA-1].Count
			}
			// If range does not include last value of its bucket, remove the repeat count from the skew estimate.
			if lessCountB <= float64(hg.Buckets[bktIndexA].Count-hg.Buckets[bktIndexA].Repeat) {
				skewEstimate -= hg.Buckets[bktIndexA].Repeat
			}
			if skewRatio > 0 {
				// Cap the max estimate to 2X the estimate, but not beyond the bucket's maximum possible row count.
				// TODO: RiskRangeSkewRatio is predominantly used for outOfRangeRowCount, and
				//       it's usage is diluated here. Consider how to address this if
				//       issues are reported here regarding under-estimation for in-bucket ranges.
				rangeEst = CalculateSkewRatioCounts(rangeEst.Est, min(rangeEst.Est*2, float64(skewEstimate)), skewRatio)
			}
			// Report the full max estimate for risk estimation usage in compareCandidates (skylinePruning).
			rangeEst.MaxEst = max(rangeEst.MaxEst, float64(skewEstimate))
		}
	}
	return rangeEst
}

// CalculateSkewRatioCounts calculates the default, min, and max skew estimates given a skew ratio.
func CalculateSkewRatioCounts(estimate, skewEstimate, skewRatio float64) RowEstimate {
	skewDiff := skewEstimate - estimate
	// Add a "ratio" of the skewEstimate to adjust the default row estimate.
	skewAmt := max(0, skewDiff*skewRatio)
	maxSkewAmt := min(skewDiff, 2*skewAmt)
	return RowEstimate{estimate + skewAmt, estimate, estimate + maxSkewAmt}
}

// RowEstimate stores the min, default, and max row count estimates.
type RowEstimate struct {
	Est    float64
	MinEst float64
	MaxEst float64
}

// DefaultRowEst returns a RowEstimate with same value for all three fields
func DefaultRowEst(est float64) RowEstimate {
	return RowEstimate{est, est, est}
}

Review comment (suggestion):
Consider adding/adjusting a unit test to lock in the intended `MinEst/MaxEst` behavior for within-bucket ranges (especially when `tidb_opt_risk_range_skew_ratio=0`), since this change alters `MaxEst` and can affect skyline pruning decisions.

Code after:
	// LessCounts are equal only if no valid buckets or both values are out of range
	isInValidBucket := lessCountA != lessCountB
	// If values in the same bucket, use skewRatio to adjust the range estimate to account for potential skew.
	if isInValidBucket && bktIndexA == bktIndexB {
		// sctx may be nil for stats version 1
		if sctx != nil {
			skewRatio := sctx.GetSessionVars().RiskRangeSkewRatio
			sctx.GetSessionVars().RecordRelevantOptVar(vardef.TiDBOptRiskRangeSkewRatio)
			// Worst case skew is if the range includes all the rows in the bucket
			skewEstimate := hg.Buckets[bktIndexA].Count
			if bktIndexA > 0 {
				skewEstimate -= hg.Buckets[bktIndexA-1].Count
			}
			// If range does not include last value of its bucket, remove the repeat count from the skew estimate.
			if lessCountB <= float64(hg.Buckets[bktIndexA].Count-hg.Buckets[bktIndexA].Repeat) {
				skewEstimate -= hg.Buckets[bktIndexA].Repeat
			}
			if skewRatio > 0 {
				// Cap the max estimate to 2X the estimate, but not beyond the bucket's maximum possible row count.
				// TODO: RiskRangeSkewRatio is predominantly used for outOfRangeRowCount, and
				//       it's usage is diluted here. Consider how to address this if
				//       issues are reported here regarding under-estimation for in-bucket ranges.
				rangeEst = CalculateSkewRatioCounts(rangeEst.Est, min(rangeEst.Est*2, float64(skewEstimate)), skewRatio)
			}
			// Report the full max estimate for risk estimation usage in compareCandidates (skylinePruning).
			rangeEst.MaxEst = max(rangeEst.MaxEst, float64(skewEstimate))
		}
	}
	return rangeEst
}

// CalculateSkewRatioCounts calculates the default, min, and max skew estimates given a skew ratio.
func CalculateSkewRatioCounts(estimate, skewEstimate, skewRatio float64) RowEstimate {
	skewDiff := skewEstimate - estimate
	// Add a "ratio" of the skewEstimate to adjust the default row estimate.
	skewAmt := max(0, skewDiff*skewRatio)
	maxSkewAmt := min(skewDiff, 2*skewAmt)
	return RowEstimate{estimate + skewAmt, estimate, estimate + maxSkewAmt}
}

// RowEstimate stores the min, default, and max row count estimates.
type RowEstimate struct {
	Est    float64
	MinEst float64
	MaxEst float64
}

// DefaultRowEst returns a RowEstimate with same value for all three fields
func DefaultRowEst(est float64) RowEstimate {
	return RowEstimate{est, est, est}
}