Files
tidb/statistics/feedback.go
2018-03-12 15:37:09 +08:00

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
}