diff --git a/advisor/heuristic.go b/advisor/heuristic.go index c35682f6b6d4f5ecfbe9dc5f79a5564d04b3d5b0..1ccab69d1bdcd092f87bbfe19e15bdd8134bee1a 100644 --- a/advisor/heuristic.go +++ b/advisor/heuristic.go @@ -2702,6 +2702,38 @@ func (q *Query4Audit) RuleUniqueKeyDup() Rule { return rule } +// RuleFulltextIndex KEY.010 +func (q *Query4Audit) RuleFulltextIndex() Rule { + var rule = q.RuleOK() + + /* // TiDB parser + for _, tiStmt := range q.TiStmt { + switch tiStmt.(type) { + case *tidb.CreateTableStmt, *tidb.AlterTableStmt: + default: + return rule + } + } + */ + switch q.Stmt.(type) { + case *sqlparser.DDL: + default: + return rule + } + + tks := ast.Tokenize(q.Query) + for _, tk := range tks { + switch tk.Type { + case ast.TokenTypeWord: + if strings.TrimSpace(strings.ToUpper(tk.Val)) == "FULLTEXT" { + rule = HeuristicRules["KEY.010"] + } + default: + } + } + return rule +} + // RuleTimestampDefault COL.013 func (q *Query4Audit) RuleTimestampDefault() Rule { var rule = q.RuleOK() diff --git a/advisor/heuristic_test.go b/advisor/heuristic_test.go index de57328c13ee88ff90d84da51e5ff6739e96ec54..8018e72e736c6414120158fd9f359a0efea35828 100644 --- a/advisor/heuristic_test.go +++ b/advisor/heuristic_test.go @@ -2130,6 +2130,7 @@ func TestCompareWithFunction(t *testing.T) { { `select id from t where substring(name,1,3)='abc';`, `SELECT * FROM tbl WHERE UNIX_TIMESTAMP(loginTime) BETWEEN UNIX_TIMESTAMP('2018-11-16 09:46:00 +0800 CST') AND UNIX_TIMESTAMP('2018-11-22 00:00:00 +0800 CST')`, + `select id from t where num/2 = 100`, }, // TODO: 右侧使用函数比较 { @@ -2596,6 +2597,47 @@ func TestRuleUniqueKeyDup(t *testing.T) { common.Log.Debug("Exiting function: %s", common.GetFunctionName()) } +// KEY.010 +func TestRuleFulltextIndex(t *testing.T) { + common.Log.Debug("Entering function: %s", common.GetFunctionName()) + sqls := [][]string{ + { + `ALTER TABLE tb ADD FULLTEXT INDEX ip (ip);`, + // `CREATE FULLTEXT INDEX ft_ip ON tb (ip);`, // TODO: tidb not support yet + `CREATE TABLE tb ( id int(10) unsigned NOT NULL AUTO_INCREMENT, ip varchar(255) NOT NULL DEFAULT '', PRIMARY KEY (id), FULLTEXT KEY ip (ip) ) ENGINE=InnoDB;`, + }, + { + `ALTER TABLE tbl add INDEX idx_col (col);`, + `CREATE INDEX part_of_name ON customer (name(10));`, + }, + } + for _, sql := range sqls[0] { + q, err := NewQuery4Audit(sql) + if err == nil { + rule := q.RuleFulltextIndex() + if rule.Item != "KEY.010" { + t.Error("Rule not match:", rule.Item, "Expect : KEY.010") + } + } else { + t.Error("sqlparser.Parse Error:", err) + } + } + + for _, sql := range sqls[1] { + q, err := NewQuery4Audit(sql) + if err == nil { + rule := q.RuleFulltextIndex() + if rule.Item != "OK" { + t.Error("Rule not match:", rule.Item, "Expect : OK") + } + } else { + t.Error("sqlparser.Parse Error:", err) + } + } + + common.Log.Debug("Exiting function: %s", common.GetFunctionName()) +} + // COL.013 func TestRuleTimestampDefault(t *testing.T) { common.Log.Debug("Entering function: %s", common.GetFunctionName()) diff --git a/advisor/rules.go b/advisor/rules.go index de925369aec6c10cce397f2486ffc60e5e356b52..dc553b424ccb1ede4a00ee0022fb255cbdbce362 100644 --- a/advisor/rules.go +++ b/advisor/rules.go @@ -796,6 +796,14 @@ func init() { Case: "CREATE UNIQUE INDEX part_of_name ON customer (name(10));", Func: (*Query4Audit).RuleUniqueKeyDup, }, + "KEY.010": { + Item: "KEY.010", + Severity: "L0", + Summary: "全文索引不是银弹", + Content: `全文索引主要用于解决模糊查询的性能问题,但需要控制好查询的频率和并发度。同时注意调整 ft_min_word_len, ft_max_word_len, ngram_token_size 等参数。`, + Case: "CREATE TABLE `tb` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `ip` varchar(255) NOT NULL DEFAULT '', PRIMARY KEY (`id`), FULLTEXT KEY `ip` (`ip`) ) ENGINE=InnoDB;", + Func: (*Query4Audit).RuleFulltextIndex, + }, "KWR.001": { Item: "KWR.001", Severity: "L2", diff --git a/advisor/testdata/TestListHeuristicRules.golden b/advisor/testdata/TestListHeuristicRules.golden index 4ce689ec0da235ab818193e5c00adae1c601e013..32dc038a9e5091daced48d7ca8d69f6abe7523fe 100644 --- a/advisor/testdata/TestListHeuristicRules.golden +++ b/advisor/testdata/TestListHeuristicRules.golden @@ -812,6 +812,16 @@ SELECT * FROM tbl ORDER BY a DESC, b ASC; ```sql CREATE UNIQUE INDEX part_of_name ON customer (name(10)); ``` +## 全文索引不是银弹 + +* **Item**:KEY.010 +* **Severity**:L0 +* **Content**:全文索引主要用于解决模糊查询的性能问题,但需要控制好查询的频率和并发度。同时注意调整 ft\_min\_word\_len, ft\_max\_word\_len, ngram\_token\_size 等参数。 +* **Case**: + +```sql +CREATE TABLE `tb` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `ip` varchar(255) NOT NULL DEFAULT '', PRIMARY KEY (`id`), FULLTEXT KEY `ip` (`ip`) ) ENGINE=InnoDB; +``` ## SQL\_CALC\_FOUND\_ROWS 效率低下 * **Item**:KWR.001 diff --git a/advisor/testdata/TestMergeConflictHeuristicRules.golden b/advisor/testdata/TestMergeConflictHeuristicRules.golden index 595b9a407c241424e0f9f9654e8bb2b356723346..a4ff936f1b68f026ad24ef0eaf599d32c71121eb 100644 --- a/advisor/testdata/TestMergeConflictHeuristicRules.golden +++ b/advisor/testdata/TestMergeConflictHeuristicRules.golden @@ -75,6 +75,7 @@ advisor.Rule{Item:"KEY.006", Severity:"L4", Summary:"主键中的列过多", Con advisor.Rule{Item:"KEY.007", Severity:"L4", Summary:"未指定主键或主键非 int 或 bigint", Content:"未指定主键或主键非 int 或 bigint,建议将主键设置为 int unsigned 或 bigint unsigned。", Case:"CREATE TABLE tbl (a int);", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}} advisor.Rule{Item:"KEY.008", Severity:"L4", Summary:"ORDER BY 多个列但排序方向不同时可能无法使用索引", Content:"在 MySQL 8.0之前当 ORDER BY 多个列指定的排序方向不同时将无法使用已经建立的索引。", Case:"SELECT * FROM tbl ORDER BY a DESC, b ASC;", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}} advisor.Rule{Item:"KEY.009", Severity:"L0", Summary:"添加唯一索引前请注意检查数据唯一性", Content:"请提前检查添加唯一索引列的数据唯一性,如果数据不唯一在线表结构调整时将有可能自动将重复列删除,这有可能导致数据丢失。", Case:"CREATE UNIQUE INDEX part_of_name ON customer (name(10));", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}} +advisor.Rule{Item:"KEY.010", Severity:"L0", Summary:"全文索引不是银弹", Content:"全文索引主要用于解决模糊查询的性能问题,但需要控制好查询的频率和并发度。同时注意调整 ft_min_word_len, ft_max_word_len, ngram_token_size 等参数。", Case:"CREATE TABLE `tb` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `ip` varchar(255) NOT NULL DEFAULT '', PRIMARY KEY (`id`), FULLTEXT KEY `ip` (`ip`) ) ENGINE=InnoDB;", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}} advisor.Rule{Item:"KWR.001", Severity:"L2", Summary:"SQL_CALC_FOUND_ROWS 效率低下", Content:"因为 SQL_CALC_FOUND_ROWS 不能很好地扩展,所以可能导致性能问题; 建议业务使用其他策略来替代 SQL_CALC_FOUND_ROWS 提供的计数功能,比如:分页结果展示等。", Case:"select SQL_CALC_FOUND_ROWS col from tbl where id>1000", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}} advisor.Rule{Item:"KWR.002", Severity:"L2", Summary:"不建议使用 MySQL 关键字做列名或表名", Content:"当使用关键字做为列名或表名时程序需要对列名和表名进行转义,如果疏忽被将导致请求无法执行。", Case:"CREATE TABLE tbl ( `select` int )", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}} advisor.Rule{Item:"KWR.003", Severity:"L1", Summary:"不建议使用复数做列名或表名", Content:"表名应该仅仅表示表里面的实体内容,不应该表示实体数量,对应于 DO 类名也是单数形式,符合表达习惯。", Case:"CREATE TABLE tbl ( `books` int )", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}} diff --git a/doc/heuristic.md b/doc/heuristic.md index 4ce689ec0da235ab818193e5c00adae1c601e013..32dc038a9e5091daced48d7ca8d69f6abe7523fe 100644 --- a/doc/heuristic.md +++ b/doc/heuristic.md @@ -812,6 +812,16 @@ SELECT * FROM tbl ORDER BY a DESC, b ASC; ```sql CREATE UNIQUE INDEX part_of_name ON customer (name(10)); ``` +## 全文索引不是银弹 + +* **Item**:KEY.010 +* **Severity**:L0 +* **Content**:全文索引主要用于解决模糊查询的性能问题,但需要控制好查询的频率和并发度。同时注意调整 ft\_min\_word\_len, ft\_max\_word\_len, ngram\_token\_size 等参数。 +* **Case**: + +```sql +CREATE TABLE `tb` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `ip` varchar(255) NOT NULL DEFAULT '', PRIMARY KEY (`id`), FULLTEXT KEY `ip` (`ip`) ) ENGINE=InnoDB; +``` ## SQL\_CALC\_FOUND\_ROWS 效率低下 * **Item**:KWR.001