@@ -21,6 +21,7 @@ import (
2121 "github.com/cockroachdb/cockroach/pkg/sql/sem/eval"
2222 "github.com/cockroachdb/cockroach/pkg/sql/sem/idxtype"
2323 "github.com/cockroachdb/cockroach/pkg/sql/sem/tree"
24+ "github.com/cockroachdb/cockroach/pkg/sql/sessiondata"
2425 "github.com/cockroachdb/cockroach/pkg/sql/stats"
2526 "github.com/cockroachdb/cockroach/pkg/sql/types"
2627 "github.com/cockroachdb/cockroach/pkg/util/buildutil"
@@ -649,6 +650,32 @@ func GetTableStats(md *opt.Metadata, tabID opt.TableID) (*props.Statistics, bool
649650 return stats , ok
650651}
651652
653+ func isEligibleFullStats (
654+ stat cat.TableStatistic , sd * sessiondata.SessionData , asOf hlc.Timestamp ,
655+ ) bool {
656+ createdAtTS := hlc.Timestamp {WallTime : stat .CreatedAt ().UnixNano ()}
657+ if stat .IsPartial () ||
658+ stat .IsMerged () && ! sd .OptimizerUseMergedPartialStatistics ||
659+ stat .IsForecast () && ! sd .OptimizerUseForecasts || createdAtTS .After (asOf ) {
660+ return false
661+ }
662+ return true
663+ }
664+
665+ // getNextFullStats returns the index of the next eligible full statistics.
666+ func getNextFullStats (
667+ start int , tab cat.Table , sd * sessiondata.SessionData , asOf hlc.Timestamp ,
668+ ) int {
669+ for i := start ; i < tab .StatisticCount (); i ++ {
670+ stat := tab .Statistic (i )
671+ if ! isEligibleFullStats (stat , sd , asOf ) {
672+ continue
673+ }
674+ return i
675+ }
676+ return - 1
677+ }
678+
652679// makeTableStatistics returns the available statistics for the given table.
653680// Statistics are derived lazily and are cached in the metadata, since they may
654681// be accessed multiple times during query optimization. For more details, see
@@ -698,31 +725,30 @@ func (sb *statisticsBuilder) makeTableStatistics(tabID opt.TableID) *props.Stati
698725 var first int
699726 sd := sb .evalCtx .SessionData ()
700727 for first < tab .StatisticCount () {
701- stat := tab .Statistic (first )
702- createdAtTS := hlc.Timestamp {WallTime : stat .CreatedAt ().UnixNano ()}
703- if stat .IsPartial () ||
704- stat .IsMerged () && ! sd .OptimizerUseMergedPartialStatistics ||
705- stat .IsForecast () && ! sd .OptimizerUseForecasts {
706- first ++
707- continue
708- } else if createdAtTS .After (asOfTs ) {
709- // The stats is too new, skip it.
710- first ++
711- continue
712- } else if statsCanaryWindow > 0 && ! useCanary && first < tab .StatisticCount ()- 1 {
713- // The following, we are getting full statistics for the stable stats,
714- // in contrast to the canary stats. The canary stats is skipped.
715- // If there remains only one full statistics, don't skip.
716- if stat .CreatedAt () == skippedStatsCreationTimestamp && ! skippedStatsCreationTimestamp .IsZero () {
717- // We've already seen this canary stat, so skip it.
718- first ++
719- continue
720- }
721- // Found a canary stats (defined as creation time within the canary window size). Register the
722- // creation timestamp and move on the next older one.
723- if createdAtTS .AddDuration (statsCanaryWindow ).After (asOfTs ) {
728+ first = getNextFullStats (first , tab , sd , asOfTs )
729+ if first < 0 {
730+ break
731+ }
732+ if statsCanaryWindow > 0 && ! useCanary {
733+ // We should use stable stats, which is defined as the second most recent full stats.
734+ stat := tab .Statistic (first )
735+ createdAtTS := hlc.Timestamp {WallTime : stat .CreatedAt ().UnixNano ()}
736+ // Check if the current full stats is the last available one. If yes,
737+ // use it as both canary and stable stats.
738+ nextFullStatsIdx := getNextFullStats (first + 1 , tab , sd , asOfTs )
739+ if nextFullStatsIdx != - 1 {
740+ // The following, we are getting full statistics for the stable stats,
741+ // in contrast to the canary stats. The canary stats is skipped.
742+ // If there remains only one full statistics, don't skip.
743+ if stat .CreatedAt () == skippedStatsCreationTimestamp && ! skippedStatsCreationTimestamp .IsZero () {
744+ // We've already seen this canary stat, so skip it.
745+ first ++
746+ continue
747+ }
748+ // Found a canary stats (defined as creation time within the canary window size). Register the
749+ // creation timestamp and move on the next older one.
724750 // If there is already a canary stats skipped, we don't skip again.
725- if skippedStatsCreationTimestamp .IsZero () {
751+ if createdAtTS . AddDuration ( statsCanaryWindow ). After ( asOfTs ) && skippedStatsCreationTimestamp .IsZero () {
726752 skippedStatsCreationTimestamp = stat .CreatedAt ()
727753 first ++
728754 continue
@@ -732,7 +758,7 @@ func (sb *statisticsBuilder) makeTableStatistics(tabID opt.TableID) *props.Stati
732758 break
733759 }
734760
735- if first >= tab .StatisticCount () {
761+ if first >= tab .StatisticCount () || first < 0 {
736762 // No statistics.
737763 stats .Available = false
738764 stats .RowCount = unknownRowCount
@@ -750,13 +776,7 @@ func (sb *statisticsBuilder) makeTableStatistics(tabID opt.TableID) *props.Stati
750776 EachStat:
751777 for i := first ; i < tab .StatisticCount (); i ++ {
752778 stat := tab .Statistic (i )
753- if stat .IsPartial () {
754- continue
755- }
756- if stat .IsMerged () && ! sb .evalCtx .SessionData ().OptimizerUseMergedPartialStatistics {
757- continue
758- }
759- if stat .IsForecast () && ! sb .evalCtx .SessionData ().OptimizerUseForecasts {
779+ if ! isEligibleFullStats (stat , sd , asOfTs ) {
760780 continue
761781 }
762782 if stat .ColumnCount () > 1 && ! sb .evalCtx .SessionData ().OptimizerUseMultiColStats {
0 commit comments