package exporter import ( "database/sql" ) 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) { 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 }