From 461b689902139a20ec0a6457dd347dbd9fd2ee55 Mon Sep 17 00:00:00 2001 From: martianzhang Date: Mon, 5 Nov 2018 09:36:16 +0800 Subject: [PATCH] extract config check into initConfig --- advisor/explainer.go | 2 +- advisor/heuristic.go | 8 +++---- advisor/index.go | 50 ++++++++++++++++++++++---------------------- advisor/rules.go | 2 +- cmd/soar/soar.go | 37 +++++++++++++++++++++++++------- common/config.go | 26 +++++++++++------------ 6 files changed, 73 insertions(+), 52 deletions(-) diff --git a/advisor/explainer.go b/advisor/explainer.go index 62c01b7..c8b7c5f 100644 --- a/advisor/explainer.go +++ b/advisor/explainer.go @@ -58,7 +58,7 @@ func checkExplainSelectType(exp *database.ExplainInfo) { if exp.ExplainFormat == database.JSONFormatExplain { // TODO - // JSON形式遍历分析不方便,转成Row格式也没有SelectType暂不处理 + // JSON 形式遍历分析不方便,转成 Row 格式也没有 SelectType 暂不处理 return } for _, v := range common.Config.ExplainWarnSelectType { diff --git a/advisor/heuristic.go b/advisor/heuristic.go index c999ffd..f125a04 100644 --- a/advisor/heuristic.go +++ b/advisor/heuristic.go @@ -1446,7 +1446,7 @@ func (q *Query4Audit) RuleSubqueryDepth() Rule { } // RuleSubQueryLimit SUB.005 -// 只有IN的SUBQUERY限制了LIMIT,FROM子句中的SUBQUERY并未限制LIMIT +// 只有 IN 的 SUBQUERY 限制了 LIMIT, FROM 子句中的 SUBQUERY 并未限制 LIMIT func (q *Query4Audit) RuleSubQueryLimit() Rule { var rule = q.RuleOK() err := sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) { @@ -1890,7 +1890,7 @@ func (idxAdv *IndexAdvisor) RuleUpdatePrimaryKey() Rule { err := sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) { switch node.(type) { case *sqlparser.UpdateExpr: - // 获取set操作的全部column + // 获取 set 操作的全部 column setColumns = append(setColumns, ast.FindAllCols(node)...) } return true, nil @@ -2820,7 +2820,7 @@ func (q *Query4Audit) RuleIntPrecision() Rule { switch col.Tp.Tp { case mysql.TypeLong: if (col.Tp.Flen < 10 || col.Tp.Flen > 11) && col.Tp.Flen > 0 { - // 有些语言ORM框架会生成int(11),有些语言的框架生成int(10) + // 有些语言 ORM 框架会生成 int(11),有些语言的框架生成 int(10) rule = HeuristicRules["COL.016"] break } @@ -2840,7 +2840,7 @@ func (q *Query4Audit) RuleIntPrecision() Rule { switch col.Tp.Tp { case mysql.TypeLong: if (col.Tp.Flen < 10 || col.Tp.Flen > 11) && col.Tp.Flen > 0 { - // 有些语言ORM框架会生成int(11),有些语言的框架生成int(10) + // 有些语言 ORM 框架会生成 int(11),有些语言的框架生成 int(10) rule = HeuristicRules["COL.016"] break } diff --git a/advisor/index.go b/advisor/index.go index 3aa1188..9a3eb20 100644 --- a/advisor/index.go +++ b/advisor/index.go @@ -48,7 +48,7 @@ type IndexInfo struct { Name string `json:"name"` // 索引名称 Database string `json:"database"` // 数据库名 Table string `json:"table"` // 表名 - DDL string `json:"ddl"` // ALTER, CREATE等类型的DDL语句 + DDL string `json:"ddl"` // ALTER, CREATE 等类型的 DDL 语句 ColumnDetails []*common.Column `json:"column_details"` // 列详情 } @@ -96,7 +96,7 @@ func NewAdvisor(env *env.VirtualEnv, rEnv database.Connector, q Query4Audit) (*I dbRef = rEnv.Database } - // DDL在Env初始化的时候已经执行过了 + // DDL 在 Env 初始化的时候已经执行过了 if _, ok := env.TableMap[dbRef]; !ok { env.TableMap[dbRef] = make(map[string]string) } @@ -279,15 +279,15 @@ func (idxAdv *IndexAdvisor) IndexAdvise() IndexAdvises { if len(idxAdv.whereINEQ) > 0 { mergeIndex(indexList, idxAdv.whereINEQ[0]) } - // 有WHERE条件,但WHERE条件未能给出索引建议就不能再加GROUP BY和ORDER BY建议了 + // 有WHERE条件,但 WHERE 条件未能给出索引建议就不能再加 GROUP BY 和 ORDER BY 建议了 if len(ignore) == 0 { - // 没有非等值查询条件时可以再为GroupBy和OrderBy添加索引 + // 没有非等值查询条件时可以再为 GroupBy 和 OrderBy 添加索引 for _, index := range idxAdv.groupBy { mergeIndex(indexList, index) } // OrderBy - // 没有GroupBy时可以为OrderBy加索引 + // 没有 GroupBy 时可以为 OrderBy 加索引 if len(idxAdv.groupBy) == 0 { for _, index := range idxAdv.orderBy { mergeIndex(indexList, index) @@ -295,14 +295,14 @@ func (idxAdv *IndexAdvisor) IndexAdvise() IndexAdvises { } } } else { - // 未指定Where条件的,只需要GroupBy和OrderBy的索引建议 + // 未指定 Where 条件的,只需要 GroupBy 和 OrderBy 的索引建议 for _, index := range idxAdv.groupBy { mergeIndex(indexList, index) } // OrderBy - // 没有GroupBy时可以为OrderBy加索引 - // 没有where条件时OrderBy的索引仅能够在索引覆盖的情况下被使用 + // 没有GroupBy 时可以为 OrderBy 加索引 + // 没有 where 条件时 OrderBy 的索引仅能够在索引覆盖的情况下被使用 // if len(idxAdv.groupBy) == 0 { // for _, index := range idxAdv.orderBy { @@ -329,8 +329,8 @@ func (idxAdv *IndexAdvisor) IndexAdvise() IndexAdvises { indexes = mergeAdvices(indexes, idxAdv.buildJoinIndex(joinTableMeta)...) if common.Config.TestDSN.Disable || common.Config.OnlineDSN.Disable { - // 无env环境下只提供单列索引,无法确定table时不给予优化建议 - // 仅有table信息时给出的建议不包含DB信息 + // 无 env 环境下只提供单列索引,无法确定 table 时不给予优化建议 + // 仅有 table 信息时给出的建议不包含 DB 信息 indexes = mergeAdvices(indexes, idxAdv.buildIndexWithNoEnv(indexList)...) } else { // 给出尽可能详细的索引建议 @@ -339,16 +339,16 @@ func (idxAdv *IndexAdvisor) IndexAdvise() IndexAdvises { indexes = mergeAdvices(indexes, subQueryAdvises...) - // 在开启env的情况下,检查数据库版本,字段类型,索引总长度 + // 在开启 env 的情况下,检查数据库版本,字段类型,索引总长度 indexes = idxAdv.idxColsTypeCheck(indexes) - // 在开启env的情况下,会对索引进行检查,对全索引进行过滤 - // 在前几步都不会对idx生成DDL语句,DDL语句在这里生成 + // 在开启 env 的情况下,会对索引进行检查,对全索引进行过滤 + // 在前几步都不会对 idx 生成 DDL 语句,DDL语句在这里生成 return idxAdv.mergeIndexes(indexes) } // idxColsTypeCheck 对超长的字段添加前缀索引,剔除无法添索引字段的列 -// TODO 暂不支持fulltext索引, +// TODO: 暂不支持 fulltext 索引, func (idxAdv *IndexAdvisor) idxColsTypeCheck(idxList []IndexInfo) []IndexInfo { if common.Config.TestDSN.Disable { return rmSelfDupIndex(idxList) @@ -363,7 +363,7 @@ func (idxAdv *IndexAdvisor) idxColsTypeCheck(idxList []IndexInfo) []IndexInfo { idxBytesTotal := 0 isOverFlow := false for _, col := range idx.ColumnDetails { - // 获取字段bytes + // 获取字段 bytes bytes := col.GetDataBytes(common.Config.OnlineDSN.Version) tmpCol := col.Name overFlow := 0 @@ -474,7 +474,7 @@ func (idxAdv *IndexAdvisor) mergeIndexes(idxList []IndexInfo) []IndexInfo { var indexes []IndexInfo for _, idx := range idxList { - // 将DB替换成vEnv中的数据库名称 + // 将 DB 替换成 vEnv 中的数据库名称 dbInVEnv := idx.Database if _, ok := idxAdv.vEnv.DBRef[idx.Database]; ok { dbInVEnv = idxAdv.vEnv.DBRef[idx.Database] @@ -503,7 +503,7 @@ func (idxAdv *IndexAdvisor) mergeIndexes(idxList []IndexInfo) []IndexInfo { var cols []string var colsDetail []*common.Column - // 把已经存在的key摘出来遍历一遍对比是否是包含关系 + // 把已经存在的 key 摘出来遍历一遍对比是否是包含关系 for _, col := range indexMeta.FindIndex(database.IndexKeyName, existedIdx.KeyName) { cols = append(cols, col.ColumnName) colsDetail = append(colsDetail, &common.Column{ @@ -532,7 +532,7 @@ func (idxAdv *IndexAdvisor) mergeIndexes(idxList []IndexInfo) []IndexInfo { } // 库、表、列名需要用反撇转义 - // TODO 关于外键索引去重的优雅解决方案 + // TODO: 关于外键索引去重的优雅解决方案 if !isConstraint { if common.Config.AllowDropIndex { alterSQL := fmt.Sprintf("alter table `%s`.`%s` drop index `%s`", idx.Database, idx.Table, idxName) @@ -736,12 +736,12 @@ func CompleteColumnsInfo(stmt sqlparser.Statement, cols []*common.Column, env *e return cols } - // 从Ast中拿到DBStructure,包含所有表的相关信息 + // 从 Ast 中拿到 DBStructure,包含所有表的相关信息 dbs := ast.GetMeta(stmt, nil) - // 此处生成的meta信息中不应该含有""db的信息,若DB为空则认为是已传入的db为默认db并进行信息补全 + // 此处生成的 meta 信息中不应该含有""db的信息,若 DB 为空则认为是已传入的 db 为默认 db 并进行信息补全 // BUG Fix: - // 修补dbs中空DB的导致后续补全列信息时无法获取正确table名称的问题 + // 修补 dbs 中空 DB 的导致后续补全列信息时无法获取正确 table 名称的问题 if _, ok := dbs[""]; ok { dbs[env.Database] = dbs[""] delete(dbs, "") @@ -829,7 +829,7 @@ func CompleteColumnsInfo(stmt sqlparser.Statement, cols []*common.Column, env *e // 将已经获取到正确表信息的列信息带入到env中,利用show columns where table 获取库表信息 // 此出会传入之前从ast中,该 db 下获取的所有表来作为where限定条件, // 防止与SQL无关的库表信息干扰准确性 - // 此处传入的是测试环境,DB是经过变换的,所以在寻找列名的时候需要将DB名称转换成测试环境中经过hash的DB名称 + // 此处传入的是测试环境,DB 是经过变换的,所以在寻找列名的时候需要将 DB 名称转换成测试环境中经过 hash 的 DB 名称 // 不然会找不到col的信息 realCols, err := env.FindColumn(col.Name, env.DBHash(db), dbs.Tables(db)...) if err != nil { @@ -840,7 +840,7 @@ func CompleteColumnsInfo(stmt sqlparser.Statement, cols []*common.Column, env *e // 对比 column 信息中的表名与从 env 中获取的库表名的一致性 for _, realCol := range realCols { if col.Name == realCol.Name { - // 如果查询到了列名一致,但从ast中获取的列的前缀与env中的表信息不符 + // 如果查询到了列名一致,但从 ast 中获取的列的前缀与 env 中的表信息不符 // 1.存在一个同名列,但不同表,该情况下忽略 // 2.存在一个未正确转换的别名(如表名为),该情况下修正,大概率是正确的 if col.Table != "" && col.Table != realCol.Table { @@ -897,7 +897,7 @@ func (idxAdv *IndexAdvisor) calcCardinality(cols []*common.Column) []*common.Col continue } - // 将获取的索引信息以db.tb维度组织到IndexMeta中 + // 将获取的索引信息以db.tb 维度组织到 IndexMeta 中 idxAdv.IndexMeta[realDB][col.Table] = indexInfo } @@ -1039,7 +1039,7 @@ func DuplicateKeyChecker(conn *database.Connector, databases ...string) map[stri } } - // 不指定DB的时候检查online dsn中的DB + // 不指定 DB 的时候检查 online dsn 中的 DB if len(databases) == 0 { databases = append(databases, tmpOnline.Database) } diff --git a/advisor/rules.go b/advisor/rules.go index 80d53f5..ab2775a 100644 --- a/advisor/rules.go +++ b/advisor/rules.go @@ -989,7 +989,7 @@ func init() { Case: "SELECT DISTINCT c.c_id, c.c_name FROM c,e WHERE e.c_id = c.c_id", Func: (*Query4Audit).RuleDistinctJoinUsage, }, - // TODO: 5.6有了semi join还要把in转成exists么? + // TODO: 5.6有了semi join 还要把 in 转成e xists 么? // Use EXISTS instead of IN to check existence of data. // http://www.winwire.com/25-tips-to-improve-sql-query-performance/ "SUB.004": { diff --git a/cmd/soar/soar.go b/cmd/soar/soar.go index 7a6acb1..6ea7a05 100644 --- a/cmd/soar/soar.go +++ b/cmd/soar/soar.go @@ -38,6 +38,7 @@ import ( func main() { // 全局变量 + var err error var sql string // 单条评审指定的 sql 或 explain sqlCounter := 1 // SQL 计数器 lineCounter := 1 // 行计数器 @@ -45,15 +46,8 @@ func main() { alterTableTimes := make(map[string]int) // 待评审的 SQL 中同一经表 ALTER 请求计数器 suggestMerged := make(map[string]map[string]advisor.Rule) // 优化建议去重, key 为 sql 的 fingerprint.ID - ex, err := os.Executable() - if err != nil { - panic(err) - } - common.BaseDir = filepath.Dir(ex) // binary 文件所在路径 - // 配置文件&命令行参数解析 - err = common.ParseConfig(common.ArgConfig()) - common.LogIfWarn(err, "") + initConfig() // 打印支持启发式建议 if common.Config.ListHeuristicRules { @@ -496,6 +490,33 @@ func main() { } } +func initConfig() { + // 更新 binary 文件所在路径为 BaseDir + ex, err := os.Executable() + if err != nil { + panic(err) + } + common.BaseDir = filepath.Dir(ex) + + for i, c := range os.Args { + // 如果指定了 -config, 它必须是第一个参数 + if strings.HasPrefix(c, "-config") && i != 1 { + fmt.Println("-config must be the first arg") + os.Exit(1) + } + // 等号两边请不要加空格 + if c == "=" { + // -config = soar.yaml not support + fmt.Println("wrong format, no space between '=', eg: -config=soar.yaml") + os.Exit(1) + } + } + + // 加载配置文件,处理命令行参数 + err = common.ParseConfig(common.ArgConfig()) + common.LogIfWarn(err, "") +} + func shutdown(vEnv *env.VirtualEnv, rEnv *database.Connector) { if common.Config.DropTestTemporary { vEnv.CleanUp() diff --git a/common/config.go b/common/config.go index be63c39..25c7d80 100644 --- a/common/config.go +++ b/common/config.go @@ -78,18 +78,18 @@ type Configration struct { RewriteRules []string `yaml:"rewrite-rules"` // 生效的重写规则 BlackList string `yaml:"blacklist"` // blacklist 中的 SQL 不会被评审,可以是指纹,也可以是正则 MaxJoinTableCount int `yaml:"max-join-table-count"` // 单条 SQL 中 JOIN 表的最大数量 - MaxGroupByColsCount int `yaml:"max-group-by-cols-count"` // 单条SQL中GroupBy包含列的最大数量 - MaxDistinctCount int `yaml:"max-distinct-count"` // 单条SQL中Distinct的最大数量 + MaxGroupByColsCount int `yaml:"max-group-by-cols-count"` // 单条 SQL 中 GroupBy 包含列的最大数量 + MaxDistinctCount int `yaml:"max-distinct-count"` // 单条 SQL 中 Distinct 的最大数量 MaxIdxColsCount int `yaml:"max-index-cols-count"` // 复合索引中包含列的最大数量 - MaxTotalRows int64 `yaml:"max-total-rows"` // 计算散粒度时,当数据行数大于 MaxTotalRows即开启数据库保护模式,散粒度返回结果可信度下降 + MaxTotalRows int64 `yaml:"max-total-rows"` // 计算散粒度时,当数据行数大于 MaxTotalRows 即开启数据库保护模式,散粒度返回结果可信度下降 MaxQueryCost int64 `yaml:"max-query-cost"` // last_query_cost 超过该值时将给予警告 SpaghettiQueryLength int `yaml:"spaghetti-query-length"` // SQL最大长度警告,超过该长度会给警告 AllowDropIndex bool `yaml:"allow-drop-index"` // 允许输出删除重复索引的建议 MaxInCount int `yaml:"max-in-count"` // IN()最大数量 MaxIdxBytesPerColumn int `yaml:"max-index-bytes-percolumn"` // 索引中单列最大字节数,默认767 MaxIdxBytes int `yaml:"max-index-bytes"` // 索引总长度限制,默认3072 - TableAllowCharsets []string `yaml:"table-allow-charsets"` // Table允许使用的DEFAULT CHARSET - TableAllowEngines []string `yaml:"table-allow-engines"` // Table允许使用的Engine + TableAllowCharsets []string `yaml:"table-allow-charsets"` // Table 允许使用的 DEFAULT CHARSET + TableAllowEngines []string `yaml:"table-allow-engines"` // Table 允许使用的 Engine MaxIdxCount int `yaml:"max-index-count"` // 单张表允许最多索引数 MaxColCount int `yaml:"max-column-count"` // 单张表允许最大列数 IdxPrefix string `yaml:"index-prefix"` // 普通索引建议使用的前缀 @@ -98,16 +98,16 @@ type Configration struct { MaxVarcharLength int `yaml:"max-varchar-length"` // varchar最大长度 // ++++++++++++++EXPLAIN检查项+++++++++++++ - ExplainSQLReportType string `yaml:"explain-sql-report-type"` // EXPLAIN markdown格式输出SQL样式,支持sample, fingerprint, pretty + ExplainSQLReportType string `yaml:"explain-sql-report-type"` // EXPLAIN markdown 格式输出 SQL 样式,支持 sample, fingerprint, pretty 等 ExplainType string `yaml:"explain-type"` // EXPLAIN方式 [traditional, extended, partitions] ExplainFormat string `yaml:"explain-format"` // FORMAT=[json, traditional] - ExplainWarnSelectType []string `yaml:"explain-warn-select-type"` // 哪些select_type不建议使用 - ExplainWarnAccessType []string `yaml:"explain-warn-access-type"` // 哪些access type不建议使用 - ExplainMaxKeyLength int `yaml:"explain-max-keys"` // 最大key_len - ExplainMinPossibleKeys int `yaml:"explain-min-keys"` // 最小possible_keys警告 + ExplainWarnSelectType []string `yaml:"explain-warn-select-type"` // 哪些 select_type 不建议使用 + ExplainWarnAccessType []string `yaml:"explain-warn-access-type"` // 哪些 access type 不建议使用 + ExplainMaxKeyLength int `yaml:"explain-max-keys"` // 最大 key_len + ExplainMinPossibleKeys int `yaml:"explain-min-keys"` // 最小 possible_keys 警告 ExplainMaxRows int `yaml:"explain-max-rows"` // 最大扫描行数警告 - ExplainWarnExtra []string `yaml:"explain-warn-extra"` // 哪些extra信息会给警告 - ExplainMaxFiltered float64 `yaml:"explain-max-filtered"` // filtered大于该配置给出警告 + ExplainWarnExtra []string `yaml:"explain-warn-extra"` // 哪些 extra 信息会给警告 + ExplainMaxFiltered float64 `yaml:"explain-max-filtered"` // filtered 大于该配置给出警告 ExplainWarnScalability []string `yaml:"explain-warn-scalability"` // 复杂度警告名单 ShowWarnings bool `yaml:"show-warnings"` // explain extended with show warnings ShowLastQueryCost bool `yaml:"show-last-query-cost"` // switch with show status like 'last_query_cost' @@ -376,7 +376,7 @@ func version() { fmt.Println("GitDirty:", GitDirty) } -// 因为vitess sqlparser使用了glog中也会使用flag,为了不让用户困扰我们单独写一个usage +// 因为vitess sqlparser 使用了 glog 中也会使用 flag,为了不让用户困扰我们单独写一个 usage func usage() { regPwd := regexp.MustCompile(`:.*@`) vitessHelp := []string{ -- GitLab