165 lines
4.7 KiB
Go
165 lines
4.7 KiB
Go
// Copyright 2024 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,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package indexadvisor
|
|
|
|
import (
|
|
"fmt"
|
|
"math"
|
|
"strings"
|
|
)
|
|
|
|
// Query represents a Query statement.
|
|
type Query struct { // DQL or DML
|
|
Alias string
|
|
SchemaName string
|
|
Text string
|
|
Frequency int
|
|
}
|
|
|
|
// Key returns the key of the Query.
|
|
func (q Query) Key() string {
|
|
return q.Text
|
|
}
|
|
|
|
// Column represents a column.
|
|
type Column struct {
|
|
SchemaName string
|
|
TableName string
|
|
ColumnName string
|
|
}
|
|
|
|
// NewColumn creates a new column.
|
|
func NewColumn(schemaName, tableName, columnName string) Column {
|
|
return Column{SchemaName: strings.ToLower(schemaName),
|
|
TableName: strings.ToLower(tableName), ColumnName: strings.ToLower(columnName)}
|
|
}
|
|
|
|
// NewColumns creates new columns.
|
|
func NewColumns(schemaName, tableName string, columnNames ...string) []Column {
|
|
cols := make([]Column, 0, len(columnNames))
|
|
for _, col := range columnNames {
|
|
cols = append(cols, NewColumn(schemaName, tableName, col))
|
|
}
|
|
return cols
|
|
}
|
|
|
|
// Key returns the key of the column.
|
|
func (c Column) Key() string {
|
|
return fmt.Sprintf("%v.%v.%v", c.SchemaName, c.TableName, c.ColumnName)
|
|
}
|
|
|
|
// Index represents an index.
|
|
type Index struct {
|
|
SchemaName string
|
|
TableName string
|
|
IndexName string
|
|
Columns []Column
|
|
}
|
|
|
|
// NewIndex creates a new index.
|
|
func NewIndex(schemaName, tableName, indexName string, columns ...string) Index {
|
|
return Index{SchemaName: strings.ToLower(schemaName), TableName: strings.ToLower(tableName),
|
|
IndexName: strings.ToLower(indexName), Columns: NewColumns(schemaName, tableName, columns...)}
|
|
}
|
|
|
|
// NewIndexWithColumns creates a new index with columns.
|
|
func NewIndexWithColumns(indexName string, columns ...Column) Index {
|
|
names := make([]string, len(columns))
|
|
for i, col := range columns {
|
|
names[i] = col.ColumnName
|
|
}
|
|
return NewIndex(columns[0].SchemaName, columns[0].TableName, indexName, names...)
|
|
}
|
|
|
|
// Key returns the key of the index.
|
|
func (i Index) Key() string {
|
|
names := make([]string, 0, len(i.Columns))
|
|
for _, col := range i.Columns {
|
|
names = append(names, col.ColumnName)
|
|
}
|
|
return fmt.Sprintf("%v.%v(%v)", i.SchemaName, i.TableName, strings.Join(names, ","))
|
|
}
|
|
|
|
// PrefixContain returns whether j is a prefix of i.
|
|
func (i Index) PrefixContain(j Index) bool {
|
|
if i.SchemaName != j.SchemaName || i.TableName != j.TableName || len(i.Columns) < len(j.Columns) {
|
|
return false
|
|
}
|
|
for k := range j.Columns {
|
|
if i.Columns[k].ColumnName != j.Columns[k].ColumnName {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
// IndexSetCost is the cost of a index configuration.
|
|
type IndexSetCost struct {
|
|
TotalWorkloadQueryCost float64
|
|
TotalNumberOfIndexColumns int
|
|
IndexKeysStr string // IndexKeysStr is the string representation of the index keys.
|
|
}
|
|
|
|
// Less returns whether the cost of c is less than the cost of other.
|
|
func (c IndexSetCost) Less(other IndexSetCost) bool {
|
|
if c.TotalWorkloadQueryCost == 0 { // not initialized
|
|
return false
|
|
}
|
|
if other.TotalWorkloadQueryCost == 0 { // not initialized
|
|
return true
|
|
}
|
|
cc, cOther := c.TotalWorkloadQueryCost, other.TotalWorkloadQueryCost
|
|
if math.Abs(cc-cOther) > 10 && math.Abs(cc-cOther)/math.Max(cc, cOther) > 0.001 {
|
|
// their cost is very different, then the less cost, the better.
|
|
return cc < cOther
|
|
}
|
|
|
|
if c.TotalNumberOfIndexColumns != other.TotalNumberOfIndexColumns {
|
|
// if they have the same cost, then the less columns, the better.
|
|
return c.TotalNumberOfIndexColumns < other.TotalNumberOfIndexColumns
|
|
}
|
|
|
|
// to make the result stable.
|
|
return c.IndexKeysStr < other.IndexKeysStr
|
|
}
|
|
|
|
// ImpactedQuery represents the impacted query.
|
|
type ImpactedQuery struct {
|
|
Query string
|
|
Improvement float64
|
|
}
|
|
|
|
// WorkloadImpact represents the workload impact.
|
|
type WorkloadImpact struct {
|
|
WorkloadImprovement float64
|
|
}
|
|
|
|
// IndexDetail represents the detail of the index.
|
|
type IndexDetail struct {
|
|
Reason string // why recommend this index
|
|
IndexSize uint64 // byte
|
|
}
|
|
|
|
// Recommendation represents the result of the index advisor.
|
|
type Recommendation struct {
|
|
Database string
|
|
Table string
|
|
IndexName string
|
|
IndexColumns []string
|
|
IndexDetail *IndexDetail
|
|
WorkloadImpact *WorkloadImpact
|
|
TopImpactedQueries []*ImpactedQuery
|
|
}
|