156 lines
4.3 KiB
Go
156 lines
4.3 KiB
Go
// Copyright 2018 PingCAP, Inc.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package statistics
|
|
|
|
import (
|
|
"bytes"
|
|
"sort"
|
|
|
|
"github.com/pingcap/tidb/kv"
|
|
"github.com/pingcap/tidb/metrics"
|
|
"github.com/pingcap/tidb/mysql"
|
|
"github.com/pingcap/tidb/tablecodec"
|
|
"github.com/pingcap/tidb/types"
|
|
"github.com/pingcap/tidb/util/ranger"
|
|
)
|
|
|
|
// `feedback` represents the total scan count in range [lower, upper).
|
|
type feedback struct {
|
|
lower *types.Datum
|
|
upper *types.Datum
|
|
count int64
|
|
}
|
|
|
|
// QueryFeedback is used to represent the query feedback info. It contains the query's scan ranges and number of rows
|
|
// in each range.
|
|
type QueryFeedback struct {
|
|
tableID int64
|
|
hist *Histogram
|
|
feedback []feedback
|
|
expected int64 // expected is the expected scan count of corresponding query.
|
|
actual int64 // actual is the actual scan count of corresponding query.
|
|
valid bool // valid represents the whether this query feedback is still valid.
|
|
desc bool // desc represents the corresponding query is desc scan.
|
|
}
|
|
|
|
// NewQueryFeedback returns a new query feedback.
|
|
func NewQueryFeedback(tableID int64, hist *Histogram, expected int64, desc bool) *QueryFeedback {
|
|
if hist != nil && hist.Len() == 0 {
|
|
hist = nil
|
|
}
|
|
return &QueryFeedback{
|
|
tableID: tableID,
|
|
valid: true,
|
|
hist: hist,
|
|
expected: expected,
|
|
desc: desc,
|
|
}
|
|
}
|
|
|
|
// StoreRanges stores the ranges for update.
|
|
func (q *QueryFeedback) StoreRanges(ranges []*ranger.NewRange) {
|
|
q.feedback = make([]feedback, 0, len(ranges))
|
|
for _, ran := range ranges {
|
|
q.feedback = append(q.feedback, feedback{&ran.LowVal[0], &ran.HighVal[0], 0})
|
|
}
|
|
}
|
|
|
|
// Invalidate is used to invalidate the query feedback.
|
|
func (q *QueryFeedback) Invalidate() {
|
|
q.feedback = nil
|
|
q.hist = nil
|
|
q.valid = false
|
|
q.actual = -1
|
|
}
|
|
|
|
// Actual gets the actual row count.
|
|
func (q *QueryFeedback) Actual() int64 {
|
|
if !q.valid {
|
|
return -1
|
|
}
|
|
return q.actual
|
|
}
|
|
|
|
// Hist gets the histogram.
|
|
func (q *QueryFeedback) Hist() *Histogram {
|
|
return q.hist
|
|
}
|
|
|
|
// Counts returns the counts info for each range. It is only used in test.
|
|
func (q *QueryFeedback) Counts() []int64 {
|
|
counts := make([]int64, 0, len(q.feedback))
|
|
for _, fb := range q.feedback {
|
|
counts = append(counts, fb.count)
|
|
}
|
|
return counts
|
|
}
|
|
|
|
// Update updates the query feedback. `startKey` is the start scan key of the partial result, used to find
|
|
// the range for update. `counts` is the scan counts of each range, used to update the feedback count info.
|
|
func (q *QueryFeedback) Update(startKey kv.Key, counts []int64) {
|
|
// Older version do not have the counts info.
|
|
if len(counts) == 0 {
|
|
q.Invalidate()
|
|
return
|
|
}
|
|
length := len(counts)
|
|
// The `counts` was the output count of each push down executor.
|
|
if counts[length-1] != -1 {
|
|
metrics.DistSQLScanKeysPartialHistogram.Observe(float64(counts[0]))
|
|
q.actual += counts[0]
|
|
return
|
|
}
|
|
// The counts is the scan count of each range now.
|
|
sum := int64(0)
|
|
rangeCounts := counts[:length-1]
|
|
for _, count := range rangeCounts {
|
|
sum += count
|
|
}
|
|
metrics.DistSQLScanKeysPartialHistogram.Observe(float64(sum))
|
|
q.actual += sum
|
|
if !q.valid || q.hist == nil {
|
|
return
|
|
}
|
|
|
|
if q.hist.tp.Tp == mysql.TypeLong {
|
|
startKey = tablecodec.CutRowKeyPrefix(startKey)
|
|
} else {
|
|
startKey = tablecodec.CutIndexPrefix(startKey)
|
|
}
|
|
// Find the range that startKey falls in.
|
|
idx := sort.Search(len(q.feedback), func(i int) bool {
|
|
return bytes.Compare(q.feedback[i].lower.GetBytes(), startKey) > 0
|
|
})
|
|
idx--
|
|
if idx < 0 {
|
|
return
|
|
}
|
|
// If the desc is true, the counts is reversed, so here we need to reverse it back.
|
|
if q.desc {
|
|
for i := 0; i < len(rangeCounts)/2; i++ {
|
|
j := len(rangeCounts) - i - 1
|
|
rangeCounts[i], rangeCounts[j] = rangeCounts[j], rangeCounts[i]
|
|
}
|
|
}
|
|
// Update the feedback count info.
|
|
for i, count := range rangeCounts {
|
|
if i+idx >= len(q.feedback) {
|
|
q.Invalidate()
|
|
break
|
|
}
|
|
q.feedback[i+idx].count += count
|
|
}
|
|
return
|
|
}
|