Skip to content

Commit 9901daf

Browse files
committed
sql/opt: unify search for latest full stat
This commit extracts the logic for iterating over all statistics on a table to find the most recent full stat that can be utilized (i.e. while paying attention to session variables that might disallow usage of forecasts and merged partial). In particular, this allows us to fix the bug where `OptimizerUseMergedPartialStatistics` was being ignored when building the scan - the impact of that is pretty minor though, so there is no release note (the variable is `true` by default, so only non-default configs would be affected, and only that `statsCreatedTime` would be reported newer than it should - only used in logs - as well as in the telemetry we'd have the wrong row count. Release note: None
1 parent 17a8a19 commit 9901daf

File tree

3 files changed

+53
-55
lines changed

3 files changed

+53
-55
lines changed

pkg/sql/opt/cat/utils.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"github.com/cockroachdb/cockroach/pkg/sql/catalog/descpb"
1414
"github.com/cockroachdb/cockroach/pkg/sql/sem/idxtype"
1515
"github.com/cockroachdb/cockroach/pkg/sql/sem/tree"
16+
"github.com/cockroachdb/cockroach/pkg/sql/sessiondata"
1617
"github.com/cockroachdb/cockroach/pkg/util/encoding"
1718
"github.com/cockroachdb/cockroach/pkg/util/treeprinter"
1819
"github.com/cockroachdb/errors"
@@ -340,3 +341,20 @@ func MaybeMarkRedactable(unsafe string, markRedactable bool) string {
340341
}
341342
return unsafe
342343
}
344+
345+
// FindLatestFullStat finds the most recent full statistic that can be used for
346+
// planning and returns the index to be used with tab.Statistic(). If such
347+
// doesn't exist (meaning that either there are no full stats altogether or that
348+
// the present ones cannot be used based on the session variables), then
349+
// tab.StatisticCount() is returned.
350+
func FindLatestFullStat(tab Table, sd *sessiondata.SessionData) int {
351+
// Stats are ordered with most recent first.
352+
var first int
353+
for first < tab.StatisticCount() &&
354+
(tab.Statistic(first).IsPartial() ||
355+
(tab.Statistic(first).IsMerged() && !sd.OptimizerUseMergedPartialStatistics) ||
356+
(tab.Statistic(first).IsForecast() && !sd.OptimizerUseForecasts)) {
357+
first++
358+
}
359+
return first
360+
}

pkg/sql/opt/exec/execbuilder/relational.go

Lines changed: 34 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -410,34 +410,24 @@ func (b *Builder) maybeAnnotateWithEstimates(node exec.Node, e memo.RelExpr) {
410410
}
411411
if scan, ok := e.(*memo.ScanExpr); ok {
412412
tab := b.mem.Metadata().Table(scan.Table)
413-
if tab.StatisticCount() > 0 {
414-
// The first stat is the most recent full one.
415-
var first int
416-
for first < tab.StatisticCount() &&
417-
(tab.Statistic(first).IsPartial() ||
418-
(tab.Statistic(first).IsMerged() && !b.evalCtx.SessionData().OptimizerUseMergedPartialStatistics) ||
419-
(tab.Statistic(first).IsForecast() && !b.evalCtx.SessionData().OptimizerUseForecasts)) {
420-
first++
413+
first := cat.FindLatestFullStat(tab, b.evalCtx.SessionData())
414+
if first < tab.StatisticCount() {
415+
stat := tab.Statistic(first)
416+
val.TableStatsRowCount = stat.RowCount()
417+
if val.TableStatsRowCount == 0 {
418+
val.TableStatsRowCount = 1
421419
}
422-
423-
if first < tab.StatisticCount() {
424-
stat := tab.Statistic(first)
425-
val.TableStatsRowCount = stat.RowCount()
426-
if val.TableStatsRowCount == 0 {
427-
val.TableStatsRowCount = 1
428-
}
429-
val.TableStatsCreatedAt = stat.CreatedAt()
430-
val.LimitHint = scan.RequiredPhysical().LimitHint
431-
val.Forecast = stat.IsForecast()
432-
if val.Forecast {
433-
val.ForecastAt = stat.CreatedAt()
434-
// Find the first non-forecast full stat.
435-
for i := first + 1; i < tab.StatisticCount(); i++ {
436-
nextStat := tab.Statistic(i)
437-
if !nextStat.IsPartial() && !nextStat.IsForecast() {
438-
val.TableStatsCreatedAt = nextStat.CreatedAt()
439-
break
440-
}
420+
val.TableStatsCreatedAt = stat.CreatedAt()
421+
val.LimitHint = scan.RequiredPhysical().LimitHint
422+
val.Forecast = stat.IsForecast()
423+
if val.Forecast {
424+
val.ForecastAt = stat.CreatedAt()
425+
// Find the first non-forecast full stat.
426+
for i := first + 1; i < tab.StatisticCount(); i++ {
427+
nextStat := tab.Statistic(i)
428+
if !nextStat.IsPartial() && !nextStat.IsForecast() {
429+
val.TableStatsCreatedAt = nextStat.CreatedAt()
430+
break
441431
}
442432
}
443433
}
@@ -898,26 +888,23 @@ func (b *Builder) buildScan(scan *memo.ScanExpr) (_ execPlan, outputCols colOrdM
898888
b.TotalScanRows += stats.RowCount
899889
b.ScanCounts[exec.ScanWithStatsCount]++
900890

901-
// The first stat is the most recent full one. Check if it was a forecast.
902-
var first int
903-
for first < tab.StatisticCount() && tab.Statistic(first).IsPartial() {
904-
first++
905-
}
891+
sd := b.evalCtx.SessionData()
892+
first := cat.FindLatestFullStat(tab, sd)
906893
if first < tab.StatisticCount() && tab.Statistic(first).IsForecast() {
907-
if b.evalCtx.SessionData().OptimizerUseForecasts {
908-
b.ScanCounts[exec.ScanWithStatsForecastCount]++
894+
b.ScanCounts[exec.ScanWithStatsForecastCount]++
909895

910-
// Calculate time since the forecast (or negative time until the forecast).
911-
nanosSinceStatsForecasted := timeutil.Since(tab.Statistic(first).CreatedAt())
912-
if nanosSinceStatsForecasted.Abs() > b.NanosSinceStatsForecasted.Abs() {
913-
b.NanosSinceStatsForecasted = nanosSinceStatsForecasted
914-
}
915-
}
916-
// Find the first non-forecast full stat.
917-
for first < tab.StatisticCount() &&
918-
(tab.Statistic(first).IsPartial() || tab.Statistic(first).IsForecast()) {
919-
first++
896+
// Calculate time since the forecast (or negative time until the forecast).
897+
nanosSinceStatsForecasted := timeutil.Since(tab.Statistic(first).CreatedAt())
898+
if nanosSinceStatsForecasted.Abs() > b.NanosSinceStatsForecasted.Abs() {
899+
b.NanosSinceStatsForecasted = nanosSinceStatsForecasted
920900
}
901+
902+
// Since currently 'first' points at the forecast, then usage of the
903+
// forecasts must be enabled, so in order to find the first full
904+
// non-forecast stat, we'll temporarily disable their usage.
905+
sd.OptimizerUseForecasts = false
906+
first = cat.FindLatestFullStat(tab, sd)
907+
sd.OptimizerUseForecasts = true
921908
}
922909

923910
if first < tab.StatisticCount() {
@@ -945,8 +932,9 @@ func (b *Builder) buildScan(scan *memo.ScanExpr) (_ execPlan, outputCols colOrdM
945932
}
946933

947934
var params exec.ScanParams
948-
params, outputCols, err = b.scanParams(tab, &scan.ScanPrivate,
949-
scan.Relational(), scan.RequiredPhysical(), statsCreatedAt)
935+
params, outputCols, err = b.scanParams(
936+
tab, &scan.ScanPrivate, scan.Relational(), scan.RequiredPhysical(), statsCreatedAt,
937+
)
950938
if err != nil {
951939
return execPlan{}, colOrdMap{}, err
952940
}

pkg/sql/opt/memo/statistics_builder.go

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -680,15 +680,7 @@ func (sb *statisticsBuilder) makeTableStatistics(tabID opt.TableID) *props.Stati
680680
// Make now and annotate the metadata table with it for next time.
681681
stats = &props.Statistics{}
682682

683-
// Find the most recent full statistic. (Stats are ordered with most recent first.)
684-
var first int
685-
for first < tab.StatisticCount() &&
686-
(tab.Statistic(first).IsPartial() ||
687-
tab.Statistic(first).IsMerged() && !sb.evalCtx.SessionData().OptimizerUseMergedPartialStatistics ||
688-
tab.Statistic(first).IsForecast() && !sb.evalCtx.SessionData().OptimizerUseForecasts) {
689-
first++
690-
}
691-
683+
first := cat.FindLatestFullStat(tab, sb.evalCtx.SessionData())
692684
if first >= tab.StatisticCount() {
693685
// No statistics.
694686
stats.Available = false

0 commit comments

Comments
 (0)