MarketingSystemDataTool/server/internal/exporter/explain.go

159 lines
3.6 KiB
Go

package exporter
import (
"database/sql"
"log"
)
type ExplainRow struct {
ID sql.NullInt64
SelectType sql.NullString
Table sql.NullString
Type sql.NullString
PossibleKeys sql.NullString
Key sql.NullString
KeyLen sql.NullString
Ref sql.NullString
Rows sql.NullInt64
Filtered sql.NullFloat64
Extra sql.NullString
}
func RunExplain(db *sql.DB, q string, args []interface{}) ([]ExplainRow, int, error) {
log.Printf("sql=EXPLAIN %s args=%v", q, args)
rows, err := db.Query("EXPLAIN "+q, args...)
if err != nil {
return nil, 0, err
}
defer rows.Close()
res := []ExplainRow{}
cols, _ := rows.Columns()
for rows.Next() {
vals := make([]interface{}, len(cols))
dest := make([]interface{}, len(cols))
for i := range vals {
dest[i] = &vals[i]
}
if err := rows.Scan(dest...); err != nil {
return nil, 0, err
}
r := ExplainRow{}
if len(cols) >= 10 {
toRow(vals, &r)
}
res = append(res, r)
}
score := 100
for _, r := range res {
if r.Type.String == "ALL" {
score -= 50
}
if r.Rows.Int64 > 1000000 {
score -= 30
}
if r.Extra.Valid {
if contains(r.Extra.String, "Using temporary") || contains(r.Extra.String, "Using filesort") {
score -= 20
}
}
}
if score < 0 {
score = 0
}
return res, score, nil
}
func toRow(vals []interface{}, r *ExplainRow) {
if s, ok := vals[0].([]byte); ok {
r.ID.Int64 = toInt64(string(s))
r.ID.Valid = true
}
if s, ok := vals[1].([]byte); ok {
r.SelectType.String = string(s)
r.SelectType.Valid = true
}
if s, ok := vals[2].([]byte); ok {
r.Table.String = string(s)
r.Table.Valid = true
}
if s, ok := vals[3].([]byte); ok {
r.Type.String = string(s)
r.Type.Valid = true
}
if s, ok := vals[4].([]byte); ok {
r.PossibleKeys.String = string(s)
r.PossibleKeys.Valid = true
}
if s, ok := vals[5].([]byte); ok {
r.Key.String = string(s)
r.Key.Valid = true
}
if s, ok := vals[6].([]byte); ok {
r.KeyLen.String = string(s)
r.KeyLen.Valid = true
}
if s, ok := vals[7].([]byte); ok {
r.Ref.String = string(s)
r.Ref.Valid = true
}
if s, ok := vals[8].([]byte); ok {
r.Rows.Int64 = toInt64(string(s))
r.Rows.Valid = true
}
if s, ok := vals[9].([]byte); ok {
r.Filtered.Float64 = toFloat64(string(s))
r.Filtered.Valid = true
}
if len(vals) > 10 {
if s, ok := vals[10].([]byte); ok {
r.Extra.String = string(s)
r.Extra.Valid = true
}
}
}
func contains(s, sub string) bool {
for i := 0; i+len(sub) <= len(s); i++ {
if s[i:i+len(sub)] == sub {
return true
}
}
return false
}
func toInt64(s string) int64 {
var n int64
for i := 0; i < len(s); i++ {
c := s[i]
if c < '0' || c > '9' {
continue
}
n = n*10 + int64(c-'0')
}
return n
}
func toFloat64(s string) float64 {
var n float64
var d float64
var seen bool
d = 1
for i := 0; i < len(s); i++ {
c := s[i]
if c == '.' {
seen = true
continue
}
if c < '0' || c > '9' {
continue
}
if !seen {
n = n*10 + float64(c-'0')
} else {
d *= 10
n = n + float64(c-'0')/d
}
}
return n
}