diff --git a/advisor/heuristic.go b/advisor/heuristic.go index d9144272f1981212a7105345be18f186608e952a..3b571614d311cfe132b38dcfa50bca9e7869c2a7 100644 --- a/advisor/heuristic.go +++ b/advisor/heuristic.go @@ -960,8 +960,8 @@ func (q *Query4Audit) RuleIPString() Rule { return rule } -// RuleDataNotQuote LIT.002 -func (q *Query4Audit) RuleDataNotQuote() Rule { +// RuleDateNotQuote LIT.002 +func (q *Query4Audit) RuleDateNotQuote() Rule { var rule = q.RuleOK() // by pass insert except, insert select @@ -984,6 +984,10 @@ func (q *Query4Audit) RuleDataNotQuote() Rule { re = regexp.MustCompile(`^['"\w-].*`) if re.FindString(sql) == "" { rule = HeuristicRules["LIT.002"] + if position := re.FindIndex([]byte(q.Query)); len(position) > 0 { + rule.Position = position[0] + } + return rule } } @@ -993,13 +997,13 @@ func (q *Query4Audit) RuleDataNotQuote() Rule { for _, sql := range sqls { re = regexp.MustCompile(`^['"\w-].*`) if re.FindString(sql) == "" { + if position := re.FindIndex([]byte(q.Query)); len(position) > 0 { + rule.Position = position[0] + } rule = HeuristicRules["LIT.002"] } } - if position := re.FindIndex([]byte(q.Query)); len(position) > 0 { - rule.Position = position[0] - } return rule } diff --git a/advisor/heuristic_test.go b/advisor/heuristic_test.go index c9af0c439709f88650baca9d16dc26c0a96b4d99..013e4eb73f363ead8c6011447d3390295709ffac 100644 --- a/advisor/heuristic_test.go +++ b/advisor/heuristic_test.go @@ -665,13 +665,14 @@ func TestRuleIPString(t *testing.T) { } // LIT.002 -func TestRuleDataNotQuote(t *testing.T) { +func TestRuleDateNotQuote(t *testing.T) { common.Log.Debug("Entering function: %s", common.GetFunctionName()) sqls := [][]string{ { "select col1,col2 from tbl where time < 2018-01-10", "select col1,col2 from tbl where time < 18-01-10", "INSERT INTO tb1 SELECT * FROM tb2 WHERE time < 2020-01-10", + `select * from tb where col < ' 2022-01-10'`, }, { "select col1,col2 from tbl where time < '2018-01-10'", @@ -684,7 +685,7 @@ func TestRuleDataNotQuote(t *testing.T) { for _, sql := range sqls[0] { q, err := NewQuery4Audit(sql) if err == nil { - rule := q.RuleDataNotQuote() + rule := q.RuleDateNotQuote() if rule.Item != "LIT.002" { t.Error("Rule not match:", rule.Item, "Expect : LIT.002") } @@ -696,7 +697,7 @@ func TestRuleDataNotQuote(t *testing.T) { for _, sql := range sqls[1] { q, err := NewQuery4Audit(sql) if err == nil { - rule := q.RuleDataNotQuote() + rule := q.RuleDateNotQuote() if rule.Item != "OK" { t.Error("Rule not match:", rule.Item, "Expect : OK") } diff --git a/advisor/rules.go b/advisor/rules.go index 0f5da8b2bde176853bc658aacb46d12c5972926b..67555d7d7637c74693c65e8f3b0ec48ec0164709 100644 --- a/advisor/rules.go +++ b/advisor/rules.go @@ -918,9 +918,9 @@ func InitHeuristicRules() { Item: "LIT.002", Severity: "L4", Summary: "日期/时间未使用引号括起", - Content: `诸如“WHERE col <2010-02-12”之类的查询是有效的SQL,但可能是一个错误,因为它将被解释为“WHERE col <1996”; 日期/时间文字应该加引号。`, + Content: `诸如“WHERE col <2010-02-12”之类的查询是有效的SQL,但可能是一个错误,因为它将被解释为“WHERE col <1996”; 日期/时间文字应该加引号,且引号前后不应有空格。`, Case: "select col1,col2 from tbl where time < 2018-01-10", - Func: (*Query4Audit).RuleDataNotQuote, + Func: (*Query4Audit).RuleDateNotQuote, }, "LIT.003": { Item: "LIT.003", diff --git a/advisor/testdata/TestListHeuristicRules.golden b/advisor/testdata/TestListHeuristicRules.golden index 41bf7c005b2e3881302b2b760248b0af500e2954..a92be37d215e29ddbe4f4ac37b467c7f5b1e8414 100644 --- a/advisor/testdata/TestListHeuristicRules.golden +++ b/advisor/testdata/TestListHeuristicRules.golden @@ -956,7 +956,7 @@ insert into tbl (IP,name) values('10.20.306.122','test') * **Item**:LIT.002 * **Severity**:L4 -* **Content**:诸如“WHERE col <2010-02-12”之类的查询是有效的SQL,但可能是一个错误,因为它将被解释为“WHERE col <1996”; 日期/时间文字应该加引号。 +* **Content**:诸如“WHERE col <2010-02-12”之类的查询是有效的SQL,但可能是一个错误,因为它将被解释为“WHERE col <1996”; 日期/时间文字应该加引号,且引号前后不应有空格。 * **Case**: ```sql diff --git a/advisor/testdata/TestMergeConflictHeuristicRules.golden b/advisor/testdata/TestMergeConflictHeuristicRules.golden index 431af4cec26bf0df4bfb653abee7415019c30ca3..cbfaba6d8cd1bab23f6cd1bae3514244b564f339 100644 --- a/advisor/testdata/TestMergeConflictHeuristicRules.golden +++ b/advisor/testdata/TestMergeConflictHeuristicRules.golden @@ -89,7 +89,7 @@ advisor.Rule{Item:"KWR.005", Severity:"L1", Summary:"SQL 中包含 unicode 特 advisor.Rule{Item:"LCK.001", Severity:"L3", Summary:"INSERT INTO xx SELECT 加锁粒度较大请谨慎", Content:"INSERT INTO xx SELECT 加锁粒度较大请谨慎", Case:"INSERT INTO tbl SELECT * FROM tbl2;", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}} advisor.Rule{Item:"LCK.002", Severity:"L3", Summary:"请慎用 INSERT ON DUPLICATE KEY UPDATE", Content:"当主键为自增键时使用 INSERT ON DUPLICATE KEY UPDATE 可能会导致主键出现大量不连续快速增长,导致主键快速溢出无法继续写入。极端情况下还有可能导致主从数据不一致。", Case:"INSERT INTO t1(a,b,c) VALUES (1,2,3) ON DUPLICATE KEY UPDATE c=c+1;", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}} advisor.Rule{Item:"LIT.001", Severity:"L2", Summary:"用字符类型存储IP地址", Content:"字符串字面上看起来像IP地址,但不是 INET_ATON() 的参数,表示数据被存储为字符而不是整数。将IP地址存储为整数更为有效。", Case:"insert into tbl (IP,name) values('10.20.306.122','test')", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}} -advisor.Rule{Item:"LIT.002", Severity:"L4", Summary:"日期/时间未使用引号括起", Content:"诸如“WHERE col <2010-02-12”之类的查询是有效的SQL,但可能是一个错误,因为它将被解释为“WHERE col <1996”; 日期/时间文字应该加引号。", Case:"select col1,col2 from tbl where time < 2018-01-10", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}} +advisor.Rule{Item:"LIT.002", Severity:"L4", Summary:"日期/时间未使用引号括起", Content:"诸如“WHERE col <2010-02-12”之类的查询是有效的SQL,但可能是一个错误,因为它将被解释为“WHERE col <1996”; 日期/时间文字应该加引号,且引号前后不应有空格。", Case:"select col1,col2 from tbl where time < 2018-01-10", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}} advisor.Rule{Item:"LIT.003", Severity:"L3", Summary:"一列中存储一系列相关数据的集合", Content:"将 ID 存储为一个列表,作为 VARCHAR/TEXT 列,这样能导致性能和数据完整性问题。查询这样的列需要使用模式匹配的表达式。使用逗号分隔的列表来做多表联结查询定位一行数据是极不优雅和耗时的。这将使验证 ID 更加困难。考虑一下,列表最多支持存放多少数据呢?将 ID 存储在一张单独的表中,代替使用多值属性,从而每个单独的属性值都可以占据一行。这样交叉表实现了两张表之间的多对多关系。这将更好地简化查询,也更有效地验证ID。", Case:"select c1,c2,c3,c4 from tab1 where col_id REGEXP '[[:<:]]12[[:>:]]'", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}} advisor.Rule{Item:"LIT.004", Severity:"L1", Summary:"请使用分号或已设定的 DELIMITER 结尾", Content:"USE database, SHOW DATABASES 等命令也需要使用使用分号或已设定的 DELIMITER 结尾。", Case:"USE db", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}} advisor.Rule{Item:"OK", Severity:"L0", Summary:"OK", Content:"OK", Case:"OK", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}} diff --git a/doc/heuristic.md b/doc/heuristic.md index 41bf7c005b2e3881302b2b760248b0af500e2954..a92be37d215e29ddbe4f4ac37b467c7f5b1e8414 100644 --- a/doc/heuristic.md +++ b/doc/heuristic.md @@ -956,7 +956,7 @@ insert into tbl (IP,name) values('10.20.306.122','test') * **Item**:LIT.002 * **Severity**:L4 -* **Content**:诸如“WHERE col <2010-02-12”之类的查询是有效的SQL,但可能是一个错误,因为它将被解释为“WHERE col <1996”; 日期/时间文字应该加引号。 +* **Content**:诸如“WHERE col <2010-02-12”之类的查询是有效的SQL,但可能是一个错误,因为它将被解释为“WHERE col <1996”; 日期/时间文字应该加引号,且引号前后不应有空格。 * **Case**: ```sql diff --git a/test/fixture/test_Run_all_test_cases.golden b/test/fixture/test_Run_all_test_cases.golden index fd31bd0e53973148817125249aea3d15377d13f1..8cee1399dfde64a9819c4bb777a54584823c2cb2 100644 --- a/test/fixture/test_Run_all_test_cases.golden +++ b/test/fixture/test_Run_all_test_cases.golden @@ -4416,7 +4416,7 @@ GROUP BY * **Content:** 为列description添加索引,散粒度为: n%; -* **Case:** ALTER TABLE \`sakila\`.\`film\` add index \`idx\_description\` (\`description\`(255)) ; +* **Case:** ALTER TABLE \`sakila\`.\`film\` add index \`idx\_description\` (\`description\`(191)) ; @@ -4530,7 +4530,7 @@ ORDER BY | id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra | |---|---|---|---|---|---|---|---|---|---|---|---|---| -| 1 | SIMPLE | *NULL* | NULL | NULL | NULL | NULL | NULL | NULL | 0 | n% | NULL | Impossible WHERE | +| 1 | SIMPLE | *t* | NULL | ALL | NULL | NULL | NULL | NULL | 600 | n% | ☠️ **O(n)** | Using where; Using filesort | @@ -4540,9 +4540,15 @@ ORDER BY * **SIMPLE**: 简单SELECT(不使用UNION或子查询等). +#### Type信息解读 + +* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描. + #### Extra信息解读 -* **Impossible WHERE**: WHERE条件过滤没有效果, 最终是全表扫描. +* **Using where**: WHERE条件用于筛选出与下一个表匹配的数据然后返回给客户端. 除非故意做的全表扫描, 否则连接类型是ALL或者是index, 且在Extra列的值中没有Using Where, 则该查询可能是有问题的. + +* ☠️ **Using filesort**: MySQL会对结果使用一个外部索引排序,而不是从表里按照索引次序读到相关内容. 可能在内存或者磁盘上进行排序. MySQL中无法利用索引完成的排序操作称为'文件排序'. ## 为sakila库的city表添加索引