forked from XiaoMi/soar
-
Notifications
You must be signed in to change notification settings - Fork 0
/
explainer.go
233 lines (203 loc) · 6.51 KB
/
explainer.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
/*
* 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
// explain建议的形式
// Item: EXP.XXX
// Severity: L[0-8]
// Summary: full table scan, not use index, full index scan...
// Content: XX TABLE xxx
// checkExplainSelectType
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))
}
}
}
}
// checkExplainAccessType 用户可以设置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) {
}
func checkExplainKeyLen(exp *database.ExplainInfo) {
}
func checkExplainKey(exp *database.ExplainInfo) {
// 小于最小使用试用key数量
//return intval($explainResult) < intval($userCond);
//explain-min-keys int
}
func checkExplainExtra(exp *database.ExplainInfo) {
// 包含用户配置的逗号分隔关键词之一则提醒
// return self::contains($explainResult, $userCond);
// explain-warn-extra []string
}
*/
// checkExplainRef ...
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"))
}
}
}
// checkExplainRows ...
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))
}
}
}
// checkExplainFiltered ...
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,
}
}
// TODO: 检查explain对应的表是否需要跳过,如dual,空表等
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)
}
}
}