diff --git a/advisor/heuristic.go b/advisor/heuristic.go index faca2d3651aa89cc100aba8624dfbe22606bcc1c..23a1b98a0a4762a295868de64ac29ab2e2fc0959 100644 --- a/advisor/heuristic.go +++ b/advisor/heuristic.go @@ -3246,6 +3246,32 @@ func (q *Query4Audit) RuleTooManyFields() Rule { return rule } +// RuleMaxTextColsCount COL.007 +func (q *Query4Audit) RuleMaxTextColsCount() Rule { + var textColsCount int + var rule = q.RuleOK() + switch q.Stmt.(type) { + case *sqlparser.DDL: + for _, tiStmt := range q.TiStmt { + switch node := tiStmt.(type) { + case *tidb.CreateTableStmt: + for _, col := range node.Cols { + switch col.Tp.Tp { + case mysql.TypeBlob, mysql.TypeLongBlob, mysql.TypeMediumBlob, mysql.TypeTinyBlob: + textColsCount++ + } + } + } + } + } + + if textColsCount > common.Config.MaxTextColsCount { + rule = HeuristicRules["COL.007"] + } + + return rule +} + // RuleAllowEngine TBL.002 func (q *Query4Audit) RuleAllowEngine() Rule { var rule = q.RuleOK() diff --git a/advisor/heuristic_test.go b/advisor/heuristic_test.go index 6ddb7a29aea1d3939c5a3795e0122319cab707a6..4ef4d21eeb85ebc3ec395b1da199d136f9f43abb 100644 --- a/advisor/heuristic_test.go +++ b/advisor/heuristic_test.go @@ -3091,6 +3091,28 @@ func TestRuleTooManyFields(t *testing.T) { common.Log.Debug("Exiting function: %s", common.GetFunctionName()) } +// COL.007 +func TestRuleMaxTextColsCount(t *testing.T) { + common.Log.Debug("Entering function: %s", common.GetFunctionName()) + sqls := []string{ + "create table tbl (a int, b text, c blob, d text);", + } + + common.Config.MaxColCount = 0 + for _, sql := range sqls { + q, err := NewQuery4Audit(sql) + if err == nil { + rule := q.RuleMaxTextColsCount() + if rule.Item != "COL.007" { + t.Error("Rule not match:", rule.Item, "Expect : COL.007") + } + } else { + t.Error("sqlparser.Parse Error:", err) + } + } + common.Log.Debug("Exiting function: %s", common.GetFunctionName()) +} + // TBL.002 func TestRuleAllowEngine(t *testing.T) { common.Log.Debug("Entering function: %s", common.GetFunctionName()) diff --git a/advisor/rules.go b/advisor/rules.go index 64e4354fa7bff1acf17b0a4d8e7e06941de0a4e6..943c92b584dcb5088ab2dbcaaa12ef16bfa1b5ba 100644 --- a/advisor/rules.go +++ b/advisor/rules.go @@ -471,6 +471,14 @@ func init() { Case: "CREATE TABLE tbl ( cols ....);", Func: (*Query4Audit).RuleTooManyFields, }, + "COL.007": { + Item: "COL.007", + Severity: "L3", + Summary: "表中包含有太多的 text/blob 列", + Content: fmt.Sprintf(`表中包含超过%d个的 text/blob 列`, common.Config.MaxTextColsCount), + Case: "CREATE TABLE tbl ( cols ....);", + Func: (*Query4Audit).RuleTooManyFields, + }, "COL.008": { Item: "COL.008", Severity: "L1", diff --git a/advisor/testdata/TestListHeuristicRules.golden b/advisor/testdata/TestListHeuristicRules.golden index 7346b5ed97419592d19a72959db2ef3f5ba06436..35ed6c4b271452baa909171dd3c101bcbb079c65 100644 --- a/advisor/testdata/TestListHeuristicRules.golden +++ b/advisor/testdata/TestListHeuristicRules.golden @@ -409,6 +409,16 @@ CREATE TABLE tbl (col int) ENGINE=InnoDB; * **Content**:表中包含有太多的列 * **Case**: +```sql +CREATE TABLE tbl ( cols ....); +``` +## 表中包含有太多的 text/blob 列 + +* **Item**:COL.007 +* **Severity**:L3 +* **Content**:表中包含超过2个的 text/blob 列 +* **Case**: + ```sql CREATE TABLE tbl ( cols ....); ``` diff --git a/advisor/testdata/TestMergeConflictHeuristicRules.golden b/advisor/testdata/TestMergeConflictHeuristicRules.golden index 9dd8a26aaebd09545cfcaba0520b1aef6d4a9f38..6821b63aa906d3c1b0ff178b25be7b71aaf9a30a 100644 --- a/advisor/testdata/TestMergeConflictHeuristicRules.golden +++ b/advisor/testdata/TestMergeConflictHeuristicRules.golden @@ -38,6 +38,7 @@ advisor.Rule{Item:"COL.003", Severity:"L2", Summary:"建议修改自增 ID 为 advisor.Rule{Item:"COL.004", Severity:"L1", Summary:"请为列添加默认值", Content:"请为列添加默认值,如果是 ALTER 操作,请不要忘记将原字段的默认值写上。字段无默认值,当表较大时无法在线变更表结构。", Case:"CREATE TABLE tbl (col int) ENGINE=InnoDB;", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}} advisor.Rule{Item:"COL.005", Severity:"L1", Summary:"列未添加注释", Content:"建议对表中每个列添加注释,来明确每个列在表中的含义及作用。", Case:"CREATE TABLE tbl (col int) ENGINE=InnoDB;", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}} advisor.Rule{Item:"COL.006", Severity:"L3", Summary:"表中包含有太多的列", Content:"表中包含有太多的列", Case:"CREATE TABLE tbl ( cols ....);", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}} +advisor.Rule{Item:"COL.007", Severity:"L3", Summary:"表中包含有太多的 text/blob 列", Content:"表中包含超过2个的 text/blob 列", Case:"CREATE TABLE tbl ( cols ....);", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}} advisor.Rule{Item:"COL.008", Severity:"L1", Summary:"可使用 VARCHAR 代替 CHAR, VARBINARY 代替 BINARY", Content:"为首先变长字段存储空间小,可以节省存储空间。其次对于查询来说,在一个相对较小的字段内搜索效率显然要高些。", Case:"create table t1(id int,name char(20),last_time date)", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}} advisor.Rule{Item:"COL.009", Severity:"L2", Summary:"建议使用精确的数据类型", Content:"实际上,任何使用 FLOAT, REAL 或 DOUBLE PRECISION 数据类型的设计都有可能是反模式。大多数应用程序使用的浮点数的取值范围并不需要达到IEEE 754标准所定义的最大/最小区间。在计算总量时,非精确浮点数所积累的影响是严重的。使用 SQL 中的 NUMERIC 或 DECIMAL 类型来代替 FLOAT 及其类似的数据类型进行固定精度的小数存储。这些数据类型精确地根据您定义这一列时指定的精度来存储数据。尽可能不要使用浮点数。", Case:"CREATE TABLE tab2 (p_id BIGINT UNSIGNED NOT NULL,a_id BIGINT UNSIGNED NOT NULL,hours float not null,PRIMARY KEY (p_id, a_id))", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}} advisor.Rule{Item:"COL.010", Severity:"L2", Summary:"不建议使用 ENUM 数据类型", Content:"ENUM 定义了列中值的类型,使用字符串表示 ENUM 里的值时,实际存储在列中的数据是这些值在定义时的序数。因此,这列的数据是字节对齐的,当您进行一次排序查询时,结果是按照实际存储的序数值排序的,而不是按字符串值的字母顺序排序的。这可能不是您所希望的。没有什么语法支持从 ENUM 或者 check 约束中添加或删除一个值;您只能使用一个新的集合重新定义这一列。如果您打算废弃一个选项,您可能会为历史数据而烦恼。作为一种策略,改变元数据——也就是说,改变表和列的定义——应该是不常见的,并且要注意测试和质量保证。有一个更好的解决方案来约束一列中的可选值:创建一张检查表,每一行包含一个允许在列中出现的候选值;然后在引用新表的旧表上声明一个外键约束。", Case:"create table tab1(status ENUM('new','in progress','fixed'))", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}} diff --git a/common/config.go b/common/config.go index 0dddded88d61a40497701dddace45673039b2a53..e21195678d1a19c70bc2c1a25ce695e11c2312ef 100644 --- a/common/config.go +++ b/common/config.go @@ -91,6 +91,7 @@ type Configuration struct { 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"` // 复合索引中包含列的最大数量 + MaxTextColsCount int `yaml:"max-text-cols-count"` // 表中含有的 text/blob 列的最大数量 MaxTotalRows int64 `yaml:"max-total-rows"` // 计算散粒度时,当数据行数大于 MaxTotalRows 即开启数据库保护模式,散粒度返回结果可信度下降 MaxQueryCost int64 `yaml:"max-query-cost"` // last_query_cost 超过该值时将给予警告 SpaghettiQueryLength int `yaml:"spaghetti-query-length"` // SQL最大长度警告,超过该长度会给警告 @@ -167,6 +168,7 @@ var Config = &Configuration{ MaxGroupByColsCount: 5, MaxDistinctCount: 5, MaxIdxColsCount: 5, + MaxTextColsCount: 2, MaxIdxBytesPerColumn: 767, MaxIdxBytes: 3072, MaxTotalRows: 9999999, @@ -528,6 +530,7 @@ func readCmdFlags() error { maxGroupByColsCount := flag.Int("max-group-by-cols-count", Config.MaxGroupByColsCount, "MaxGroupByColsCount, 单条 SQL 中 GroupBy 包含列的最大数量") maxDistinctCount := flag.Int("max-distinct-count", Config.MaxDistinctCount, "MaxDistinctCount, 单条 SQL 中 Distinct 的最大数量") maxIdxColsCount := flag.Int("max-index-cols-count", Config.MaxIdxColsCount, "MaxIdxColsCount, 复合索引中包含列的最大数量") + maxTextColsCount := flag.Int("max-texst-cols-count", Config.MaxTextColsCount, "MaxTextColsCount, 表中含有的 text/blob 列的最大数量") maxTotalRows := flag.Int64("max-total-rows", Config.MaxTotalRows, "MaxTotalRows, 计算散粒度时,当数据行数大于MaxTotalRows即开启数据库保护模式,不计算散粒度") maxQueryCost := flag.Int64("max-query-cost", Config.MaxQueryCost, "MaxQueryCost, last_query_cost 超过该值时将给予警告") spaghettiQueryLength := flag.Int("spaghetti-query-length", Config.SpaghettiQueryLength, "SpaghettiQueryLength, SQL最大长度警告,超过该长度会给警告") @@ -626,6 +629,7 @@ func readCmdFlags() error { Config.MaxIdxColsCount = 16 } + Config.MaxTextColsCount = *maxTextColsCount Config.MaxIdxBytesPerColumn = *maxIdxBytesPerColumn Config.MaxIdxBytes = *maxIdxBytes if *allowCharsets != "" { diff --git a/common/logger.go b/common/logger.go index 68dda20178f9a5d678ac0df46281c213207cf244..74b6d6433911b56e97a0bcbedd271c335e406158 100644 --- a/common/logger.go +++ b/common/logger.go @@ -43,11 +43,11 @@ func LoggerInit() { func() { _ = Log.DelLogger(logs.AdapterFile) }() logConfig := map[string]interface{}{ "filename": Config.LogOutput, - "level": 7, + "level": 7, "maxlines": 0, - "maxsize": 0, - "daily": false, - "maxdays": 0, + "maxsize": 0, + "daily": false, + "maxdays": 0, } logConfigJson, _ := json.Marshal(logConfig) err := Log.SetLogger(logs.AdapterFile, fmt.Sprintf(string(logConfigJson))) diff --git a/doc/heuristic.md b/doc/heuristic.md index 7346b5ed97419592d19a72959db2ef3f5ba06436..35ed6c4b271452baa909171dd3c101bcbb079c65 100644 --- a/doc/heuristic.md +++ b/doc/heuristic.md @@ -409,6 +409,16 @@ CREATE TABLE tbl (col int) ENGINE=InnoDB; * **Content**:表中包含有太多的列 * **Case**: +```sql +CREATE TABLE tbl ( cols ....); +``` +## 表中包含有太多的 text/blob 列 + +* **Item**:COL.007 +* **Severity**:L3 +* **Content**:表中包含超过2个的 text/blob 列 +* **Case**: + ```sql CREATE TABLE tbl ( cols ....); ```