/* * Copyright 2018 Xiaomi, 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 advisor import ( "fmt" "strings" "github.com/XiaoMi/soar/common" "github.com/XiaoMi/soar/database" ) var explainRuleID int // [EXP.XXX]Rule var explainRules map[string]Rule // [table_name]"suggest text" var tablesSuggests map[string][]string /* var explainIgnoreTables = []string{ "dual", "", } */ // explain建议的形式 // Item: EXP.XXX // Severity: L[0-8] // Summary: full table scan, not use index, full index scan... // Content: XX TABLE xxx // func checkExplainSelectType(exp *database.ExplainInfo) { // 判断是否跳过不检查 if len(common.Config.ExplainWarnSelectType) == 1 { if common.Config.ExplainWarnSelectType[0] == "" { return } } else if len(common.Config.ExplainWarnSelectType) < 1 { return } if exp.ExplainFormat == database.JSONFormatExplain { // TODO // JSON形式遍历分析不方便,转成Row格式也没有SelectType暂不处理 return } for _, v := range common.Config.ExplainWarnSelectType { for _, row := range exp.ExplainRows { if row.SelectType == v && v != "" { tablesSuggests[row.TableName] = append(tablesSuggests[row.TableName], fmt.Sprintf("SelectType:%s", row.SelectType)) } } } } // 用户可以设置AccessType的建议级别,匹配到的查询会给出建议 func checkExplainAccessType(exp *database.ExplainInfo) { // 判断是否跳过不检查 if len(common.Config.ExplainWarnAccessType) == 1 { if common.Config.ExplainWarnAccessType[0] == "" { return } } else if len(common.Config.ExplainWarnAccessType) < 1 { return } rows := exp.ExplainRows if exp.ExplainFormat == database.JSONFormatExplain { // JSON形式遍历分析不方便,转成Row格式统一处理 rows = database.ConvertExplainJSON2Row(exp.ExplainJSON) } for _, v := range common.Config.ExplainWarnAccessType { for _, row := range rows { if row.AccessType == v && v != "" { tablesSuggests[row.TableName] = append(tablesSuggests[row.TableName], fmt.Sprintf("Scalability:%s", row.Scalability)) } } } } // TODO: /* func checkExplainPossibleKeys(exp *database.ExplainInfo) { // 判断是否跳过不检查 if common.Config.ExplainMinPossibleKeys == 0 { return } rows := exp.ExplainRows if exp.ExplainFormat == database.JSONFormatExplain { // JSON形式遍历分析不方便,转成Row格式统一处理 rows = database.ConvertExplainJSON2Row(exp.ExplainJSON) } for _, row := range rows { if len(row.PossibleKeys) < common.Config.ExplainMinPossibleKeys { tablesSuggests[row.TableName] = append(tablesSuggests[row.TableName], fmt.Sprintf("PossibleKeys:%d < %d", len(row.PossibleKeys), common.Config.ExplainMinPossibleKeys)) } } } */ // TODO: /* func checkExplainKeyLen(exp *database.ExplainInfo) { } */ // TODO: /* func checkExplainKey(exp *database.ExplainInfo) { // 小于最小使用试用key数量 //return intval($explainResult) < intval($userCond); //explain-min-keys int } */ func checkExplainRef(exp *database.ExplainInfo) { rows := exp.ExplainRows if exp.ExplainFormat == database.JSONFormatExplain { // JSON形式遍历分析不方便,转成Row格式统一处理 rows = database.ConvertExplainJSON2Row(exp.ExplainJSON) } for i, row := range rows { if strings.Join(row.Ref, "") == "NULL" || strings.Join(row.Ref, "") == "" { if i == 0 && len(rows) > 1 { continue } tablesSuggests[row.TableName] = append(tablesSuggests[row.TableName], fmt.Sprintf("Ref:null")) } } } func checkExplainRows(exp *database.ExplainInfo) { // 判断是否跳过不检查 if common.Config.ExplainMaxRows <= 0 { return } rows := exp.ExplainRows if exp.ExplainFormat == database.JSONFormatExplain { // JSON形式遍历分析不方便,转成Row格式统一处理 rows = database.ConvertExplainJSON2Row(exp.ExplainJSON) } for _, row := range rows { if row.Rows >= common.Config.ExplainMaxRows { tablesSuggests[row.TableName] = append(tablesSuggests[row.TableName], fmt.Sprintf("Rows:%d", row.Rows)) } } } // TODO: /* func checkExplainExtra(exp *database.ExplainInfo) { // 包含用户配置的逗号分隔关键词之一则提醒 // return self::contains($explainResult, $userCond); // explain-warn-extra []string } */ func checkExplainFiltered(exp *database.ExplainInfo) { // 判断是否跳过不检查 if common.Config.ExplainMaxFiltered <= 0.001 { return } rows := exp.ExplainRows if exp.ExplainFormat == database.JSONFormatExplain { // JSON形式遍历分析不方便,转成Row格式统一处理 rows = database.ConvertExplainJSON2Row(exp.ExplainJSON) } for i, row := range rows { if i == 0 && len(rows) > 1 { continue } if row.Filtered >= common.Config.ExplainMaxFiltered { tablesSuggests[row.TableName] = append(tablesSuggests[row.TableName], fmt.Sprintf("Filtered:%.2f%s", row.Filtered, "%")) } } } // ExplainAdvisor 基于explain信息给出建议 func ExplainAdvisor(exp *database.ExplainInfo) map[string]Rule { common.Log.Debug("ExplainAdvisor SQL: %v", exp.SQL) explainRuleID = 0 explainRules = make(map[string]Rule) tablesSuggests = make(map[string][]string) checkExplainSelectType(exp) checkExplainAccessType(exp) checkExplainFiltered(exp) checkExplainRef(exp) checkExplainRows(exp) // 打印explain table content := database.PrintMarkdownExplainTable(exp) if common.Config.ShowWarnings { content += "\n" + database.MySQLExplainWarnings(exp) } // 对explain table中各项难于理解的值做解释 cases := database.ExplainInfoTranslator(exp) // 添加last_query_cost if common.Config.ShowLastQueryCost { content += "\n" + database.MySQLExplainQueryCost(exp) } if content != "" { explainRules["EXP.000"] = Rule{ Item: "EXP.000", Severity: "L0", Summary: "Explain信息", Content: content, Case: cases, Func: (*Query4Audit).RuleOK, } } /* for t, s := range tablesSuggests { // 检查explain对应的表是否需要跳过,如dual,空表等 ig := false for _, ti := range explainIgnoreTables { if ti == t { ig = true } } if ig { continue } ruleId := fmt.Sprintf("EXP.%03d", explainRuleId+1) explainRuleId = explainRuleId + 1 explainRules[ruleId] = Rule{ Item: ruleId, Severity: "L0", Summary: fmt.Sprintf("表 `%s` 查询效率不高", t), Content: fmt.Sprint("原因:", strings.Join(s, ",")), Case: "", Func: (*Query4Audit).RuleOK, } } */ return explainRules } // DigestExplainText 分析用户输入的EXPLAIN信息 func DigestExplainText(text string) { // explain信息就不要显示完美了,美不美自己看吧。 common.Config.IgnoreRules = append(common.Config.IgnoreRules, "OK") if !IsIgnoreRule("EXP.") { explainInfo, err := database.ParseExplainText(text) if err != nil { common.Log.Error("main ParseExplainText Error: %v", err) return } expSuggest := ExplainAdvisor(explainInfo) _, output := FormatSuggest("", common.Config.ReportType, expSuggest) if common.Config.ReportType == "html" { fmt.Println(common.MarkdownHTMLHeader()) fmt.Println(common.Markdown2HTML(output)) } else { fmt.Println(output) } } }