diff --git a/advisor/rules.go b/advisor/rules.go index 1fae6c4a85fad5bed015760fed96b0a82a8d44e3..beadb79a69afb20b481809d4f67438c400930f8e 100644 --- a/advisor/rules.go +++ b/advisor/rules.go @@ -549,7 +549,7 @@ func init() { Item: "COL.017", Severity: "L2", Summary: "VARCHAR 定义长度过长", - Content: fmt.Sprintf(`varchar 是可变长字符串,不预先分配存储空间,长度不要超过%d,如果存储长度过长MySQL将定义字段类型为text,独立出来一张表,用主键来对应,避免影响其它字段索引效率。`, common.Config.MaxVarcharLength), + Content: fmt.Sprintf(`varchar 是可变长字符串,不预先分配存储空间,长度不要超过%d,如果存储长度过长 MySQL 将定义字段类型为 text,独立出来一张表,用主键来对应,避免影响其它字段索引效率。`, common.Config.MaxVarcharLength), Case: "CREATE TABLE tab (a varchar(3500));", Func: (*Query4Audit).RuleVarcharLength, }, @@ -655,7 +655,7 @@ func init() { Item: "JOI.003", Severity: "L4", Summary: "OUTER JOIN 失效", - Content: `由于 WHERE 条件错误使得 OUTER JOIN 的外部表无数据返回,这会将查询隐式转换为 INNER JOIN 。如:select c from L left join R using(c) where L.a=5 and R.b=10。这种 SQL 逻辑上可能存在错误或程序员对 OUTER JOIN 如何工作存在误解,因为LEFT/RIGHT JOIN是LEFT/RIGHT OUTER JOIN的缩写。`, + Content: `由于 WHERE 条件错误使得 OUTER JOIN 的外部表无数据返回,这会将查询隐式转换为 INNER JOIN 。如:select c from L left join R using(c) where L.a=5 and R.b=10。这种 SQL 逻辑上可能存在错误或程序员对 OUTER JOIN 如何工作存在误解,因为 LEFT/RIGHT JOIN 是 LEFT/RIGHT OUTER JOIN 的缩写。`, Case: "select c1,c2,c3 from t1 left outer join t2 using(c1) where t1.c2=2 and t2.c3=4", Func: (*Query4Audit).RuleOK, // TODO }, @@ -848,8 +848,8 @@ func init() { "RES.001": { Item: "RES.001", Severity: "L4", - Summary: "非确定性的GROUP BY", - Content: `SQL返回的列既不在聚合函数中也不是GROUP BY表达式的列中,因此这些值的结果将是非确定性的。如:select a, b, c from tbl where foo="bar" group by a,该SQL返回的结果就是不确定的。`, + Summary: "非确定性的 GROUP BY", + Content: `SQL返回的列既不在聚合函数中也不是 GROUP BY 表达式的列中,因此这些值的结果将是非确定性的。如:select a, b, c from tbl where foo="bar" group by a,该 SQL 返回的结果就是不确定的。`, Case: "select c1,c2,c3 from t1 where c2='foo' group by c2", Func: (*Query4Audit).RuleNoDeterministicGroupby, }, @@ -864,24 +864,24 @@ func init() { "RES.003": { Item: "RES.003", Severity: "L4", - Summary: "UPDATE/DELETE操作使用了LIMIT条件", - Content: `UPDATE/DELETE操作使用LIMIT条件和不添加WHERE条件一样危险,它可将会导致主从数据不一致或从库同步中断。`, + Summary: "UPDATE/DELETE 操作使用了 LIMIT 条件", + Content: `UPDATE/DELETE 操作使用 LIMIT 条件和不添加 WHERE 条件一样危险,它可将会导致主从数据不一致或从库同步中断。`, Case: "UPDATE film SET length = 120 WHERE title = 'abc' LIMIT 1;", Func: (*Query4Audit).RuleUpdateDeleteWithLimit, }, "RES.004": { Item: "RES.004", Severity: "L4", - Summary: "UPDATE/DELETE操作指定了ORDER BY条件", - Content: `UPDATE/DELETE操作不要指定ORDER BY条件。`, + Summary: "UPDATE/DELETE 操作指定了 ORDER BY 条件", + Content: `UPDATE/DELETE 操作不要指定 ORDER BY 条件。`, Case: "UPDATE film SET length = 120 WHERE title = 'abc' ORDER BY title", Func: (*Query4Audit).RuleUpdateDeleteWithOrderby, }, "RES.005": { Item: "RES.005", Severity: "L4", - Summary: "UPDATE可能存在逻辑错误,导致数据损坏", - Content: "在一条UPDATE语句中,如果要更新多个字段,字段间不能使用 AND ,而应该用逗号分隔。", + Summary: "UPDATE 语句可能存在逻辑错误,导致数据损坏", + Content: "在一条 UPDATE 语句中,如果要更新多个字段,字段间不能使用 AND ,而应该用逗号分隔。", Case: "update tbl set col = 1 and cl = 2 where col=3;", Func: (*Query4Audit).RuleUpdateSetAnd, }, @@ -913,7 +913,7 @@ func init() { Item: "SEC.001", Severity: "L0", Summary: "请谨慎使用TRUNCATE操作", - Content: `一般来说想清空一张表最快速的做法就是使用TRUNCATE TABLE tbl_name;语句。但TRUNCATE操作也并非是毫无代价的,TRUNCATE TABLE无法返回被删除的准确行数,如果需要返回被删除的行数建议使用DELETE语法。TRUNCATE操作还会重置AUTO_INCREMENT,如果不想重置该值建议使用DELETE FROM tbl_name WHERE 1;替代。TRUNCATE操作会对数据字典添加源数据锁(MDL),当一次需要TRUNCATE很多表时会影响整个实例的所有请求,因此如果要TRUNCATE多个表建议用DROP+CREATE的方式以减少锁时长。`, + Content: `一般来说想清空一张表最快速的做法就是使用TRUNCATE TABLE tbl_name;语句。但TRUNCATE操作也并非是毫无代价的,TRUNCATE TABLE无法返回被删除的准确行数,如果需要返回被删除的行数建议使用DELETE语法。TRUNCATE 操作还会重置 AUTO_INCREMENT,如果不想重置该值建议使用 DELETE FROM tbl_name WHERE 1;替代。TRUNCATE 操作会对数据字典添加源数据锁(MDL),当一次需要 TRUNCATE 很多表时会影响整个实例的所有请求,因此如果要 TRUNCATE 多个表建议用 DROP+CREATE 的方式以减少锁时长。`, Case: "TRUNCATE TABLE tbl_name", Func: (*Query4Audit).RuleTruncateTable, }, @@ -968,8 +968,8 @@ func init() { "SUB.001": { Item: "SUB.001", Severity: "L4", - Summary: "MySQL对子查询的优化效果不佳", - Content: `MySQL将外部查询中的每一行作为依赖子查询执行子查询。 这是导致严重性能问题的常见原因。这可能会在 MySQL 5.6版本中得到改善, 但对于5.1及更早版本, 建议将该类查询分别重写为JOIN或LEFT OUTER JOIN。`, + Summary: "MySQL 对子查询的优化效果不佳", + Content: `MySQL 将外部查询中的每一行作为依赖子查询执行子查询。 这是导致严重性能问题的常见原因。这可能会在 MySQL 5.6 版本中得到改善, 但对于5.1及更早版本, 建议将该类查询分别重写为 JOIN 或 LEFT OUTER JOIN。`, Case: "select col1,col2,col3 from table1 where col2 in(select col from table2)", Func: (*Query4Audit).RuleInSubquery, }, diff --git a/advisor/testdata/TestListHeuristicRules.golden b/advisor/testdata/TestListHeuristicRules.golden index 0699a019e2ca10a749af3be9191e8dffd1b07a0f..bf63b17bdd4f840f21a4dfb47c460036465cb189 100644 --- a/advisor/testdata/TestListHeuristicRules.golden +++ b/advisor/testdata/TestListHeuristicRules.golden @@ -506,7 +506,7 @@ CREATE TABLE tab (a INT(1)); * **Item**:COL.017 * **Severity**:L2 -* **Content**:varchar 是可变长字符串,不预先分配存储空间,长度不要超过1024,如果存储长度过长MySQL将定义字段类型为text,独立出来一张表,用主键来对应,避免影响其它字段索引效率。 +* **Content**:varchar 是可变长字符串,不预先分配存储空间,长度不要超过1024,如果存储长度过长 MySQL 将定义字段类型为 text,独立出来一张表,用主键来对应,避免影响其它字段索引效率。 * **Case**: ```sql @@ -636,7 +636,7 @@ select tb1.col from (tb1, tb2) join tb2 on tb1.id=tb.id where tb1.id=1 * **Item**:JOI.003 * **Severity**:L4 -* **Content**:由于 WHERE 条件错误使得 OUTER JOIN 的外部表无数据返回,这会将查询隐式转换为 INNER JOIN 。如:select c from L left join R using(c) where L.a=5 and R.b=10。这种 SQL 逻辑上可能存在错误或程序员对 OUTER JOIN 如何工作存在误解,因为LEFT/RIGHT JOIN是LEFT/RIGHT OUTER JOIN的缩写。 +* **Content**:由于 WHERE 条件错误使得 OUTER JOIN 的外部表无数据返回,这会将查询隐式转换为 INNER JOIN 。如:select c from L left join R using(c) where L.a=5 and R.b=10。这种 SQL 逻辑上可能存在错误或程序员对 OUTER JOIN 如何工作存在误解,因为 LEFT/RIGHT JOIN 是 LEFT/RIGHT OUTER JOIN 的缩写。 * **Case**: ```sql @@ -872,11 +872,11 @@ select c1,c2,c3,c4 from tab1 where col_id REGEXP '[[:<:]]12[[:>:]]' ```sql USE db ``` -## 非确定性的GROUP BY +## 非确定性的 GROUP BY * **Item**:RES.001 * **Severity**:L4 -* **Content**:SQL返回的列既不在聚合函数中也不是GROUP BY表达式的列中,因此这些值的结果将是非确定性的。如:select a, b, c from tbl where foo="bar" group by a,该SQL返回的结果就是不确定的。 +* **Content**:SQL返回的列既不在聚合函数中也不是 GROUP BY 表达式的列中,因此这些值的结果将是非确定性的。如:select a, b, c from tbl where foo="bar" group by a,该 SQL 返回的结果就是不确定的。 * **Case**: ```sql @@ -892,31 +892,31 @@ select c1,c2,c3 from t1 where c2='foo' group by c2 ```sql select col1,col2 from tbl where name=xx limit 10 ``` -## UPDATE/DELETE操作使用了LIMIT条件 +## UPDATE/DELETE 操作使用了 LIMIT 条件 * **Item**:RES.003 * **Severity**:L4 -* **Content**:UPDATE/DELETE操作使用LIMIT条件和不添加WHERE条件一样危险,它可将会导致主从数据不一致或从库同步中断。 +* **Content**:UPDATE/DELETE 操作使用 LIMIT 条件和不添加 WHERE 条件一样危险,它可将会导致主从数据不一致或从库同步中断。 * **Case**: ```sql UPDATE film SET length = 120 WHERE title = 'abc' LIMIT 1; ``` -## UPDATE/DELETE操作指定了ORDER BY条件 +## UPDATE/DELETE 操作指定了 ORDER BY 条件 * **Item**:RES.004 * **Severity**:L4 -* **Content**:UPDATE/DELETE操作不要指定ORDER BY条件。 +* **Content**:UPDATE/DELETE 操作不要指定 ORDER BY 条件。 * **Case**: ```sql UPDATE film SET length = 120 WHERE title = 'abc' ORDER BY title ``` -## UPDATE可能存在逻辑错误,导致数据损坏 +## UPDATE 语句可能存在逻辑错误,导致数据损坏 * **Item**:RES.005 * **Severity**:L4 -* **Content**:在一条UPDATE语句中,如果要更新多个字段,字段间不能使用 AND ,而应该用逗号分隔。 +* **Content**:在一条 UPDATE 语句中,如果要更新多个字段,字段间不能使用 AND ,而应该用逗号分隔。 * **Case**: ```sql @@ -956,7 +956,7 @@ LOAD DATA INFILE 'data.txt' INTO TABLE db2.my_table; * **Item**:SEC.001 * **Severity**:L0 -* **Content**:一般来说想清空一张表最快速的做法就是使用TRUNCATE TABLE tbl\_name;语句。但TRUNCATE操作也并非是毫无代价的,TRUNCATE TABLE无法返回被删除的准确行数,如果需要返回被删除的行数建议使用DELETE语法。TRUNCATE操作还会重置AUTO\_INCREMENT,如果不想重置该值建议使用DELETE FROM tbl\_name WHERE 1;替代。TRUNCATE操作会对数据字典添加源数据锁(MDL),当一次需要TRUNCATE很多表时会影响整个实例的所有请求,因此如果要TRUNCATE多个表建议用DROP+CREATE的方式以减少锁时长。 +* **Content**:一般来说想清空一张表最快速的做法就是使用TRUNCATE TABLE tbl\_name;语句。但TRUNCATE操作也并非是毫无代价的,TRUNCATE TABLE无法返回被删除的准确行数,如果需要返回被删除的行数建议使用DELETE语法。TRUNCATE 操作还会重置 AUTO\_INCREMENT,如果不想重置该值建议使用 DELETE FROM tbl\_name WHERE 1;替代。TRUNCATE 操作会对数据字典添加源数据锁(MDL),当一次需要 TRUNCATE 很多表时会影响整个实例的所有请求,因此如果要 TRUNCATE 多个表建议用 DROP+CREATE 的方式以减少锁时长。 * **Case**: ```sql @@ -1022,11 +1022,11 @@ select col from now where type!=0 ```sql CREATE TABLE ` abc` (a int); ``` -## MySQL对子查询的优化效果不佳 +## MySQL 对子查询的优化效果不佳 * **Item**:SUB.001 * **Severity**:L4 -* **Content**:MySQL将外部查询中的每一行作为依赖子查询执行子查询。 这是导致严重性能问题的常见原因。这可能会在 MySQL 5.6版本中得到改善, 但对于5.1及更早版本, 建议将该类查询分别重写为JOIN或LEFT OUTER JOIN。 +* **Content**:MySQL 将外部查询中的每一行作为依赖子查询执行子查询。 这是导致严重性能问题的常见原因。这可能会在 MySQL 5.6 版本中得到改善, 但对于5.1及更早版本, 建议将该类查询分别重写为 JOIN 或 LEFT OUTER JOIN。 * **Case**: ```sql diff --git a/advisor/testdata/TestMergeConflictHeuristicRules.golden b/advisor/testdata/TestMergeConflictHeuristicRules.golden index 31c3618e4ce589cbd39196d89c277386fdd1d4bc..3c88360cad219e3c35d2604989066de9e2ed0de3 100644 --- a/advisor/testdata/TestMergeConflictHeuristicRules.golden +++ b/advisor/testdata/TestMergeConflictHeuristicRules.golden @@ -47,7 +47,7 @@ advisor.Rule{Item:"COL.013", Severity:"L4", Summary:"TIMESTAMP 类型未设置 advisor.Rule{Item:"COL.014", Severity:"L5", Summary:"为列指定了字符集", Content:"建议列与表使用同一个字符集,不要单独指定列的字符集。", Case:"CREATE TABLE `tb2` ( `id` int(11) DEFAULT NULL, `col` char(10) CHARACTER SET utf8 DEFAULT NULL)", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}} advisor.Rule{Item:"COL.015", Severity:"L4", Summary:"BLOB 类型的字段不可指定默认值", Content:"BLOB 类型的字段不可指定默认值", Case:"CREATE TABLE `tbl` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `c` blob NOT NULL DEFAULT '', PRIMARY KEY (`id`));", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}} advisor.Rule{Item:"COL.016", Severity:"L1", Summary:"整型定义建议采用 INT(10) 或 BIGINT(20)", Content:"INT(M) 在 integer 数据类型中,M 表示最大显示宽度。 在 INT(M) 中,M 的值跟 INT(M) 所占多少存储空间并无任何关系。 INT(3)、INT(4)、INT(8) 在磁盘上都是占用 4 bytes 的存储空间。", Case:"CREATE TABLE tab (a INT(1));", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}} -advisor.Rule{Item:"COL.017", Severity:"L2", Summary:"VARCHAR 定义长度过长", Content:"varchar 是可变长字符串,不预先分配存储空间,长度不要超过1024,如果存储长度过长MySQL将定义字段类型为text,独立出来一张表,用主键来对应,避免影响其它字段索引效率。", Case:"CREATE TABLE tab (a varchar(3500));", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}} +advisor.Rule{Item:"COL.017", Severity:"L2", Summary:"VARCHAR 定义长度过长", Content:"varchar 是可变长字符串,不预先分配存储空间,长度不要超过1024,如果存储长度过长 MySQL 将定义字段类型为 text,独立出来一张表,用主键来对应,避免影响其它字段索引效率。", Case:"CREATE TABLE tab (a varchar(3500));", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}} advisor.Rule{Item:"DIS.001", Severity:"L1", Summary:"消除不必要的 DISTINCT 条件", Content:"太多DISTINCT条件是复杂的裹脚布式查询的症状。考虑将复杂查询分解成许多简单的查询,并减少DISTINCT条件的数量。如果主键列是列的结果集的一部分,则DISTINCT条件可能没有影响。", Case:"SELECT DISTINCT c.c_id,count(DISTINCT c.c_name),count(DISTINCT c.c_e),count(DISTINCT c.c_n),count(DISTINCT c.c_me),c.c_d FROM (select distinct id, name from B) as e WHERE e.country_id = c.country_id", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}} advisor.Rule{Item:"DIS.002", Severity:"L3", Summary:"COUNT(DISTINCT) 多列时结果可能和你预想的不同", Content:"COUNT(DISTINCT col) 计算该列除NULL之外的不重复行数,注意 COUNT(DISTINCT col, col2) 如果其中一列全为 NULL 那么即使另一列有不同的值,也返回0。", Case:"SELECT COUNT(DISTINCT col, col2) FROM tbl;", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}} advisor.Rule{Item:"DIS.003", Severity:"L3", Summary:"DISTINCT * 对有主键的表没有意义", Content:"当表已经有主键时,对所有列进行 DISTINCT 的输出结果与不进行 DISTINCT 操作的结果相同,请不要画蛇添足。", Case:"SELECT DISTINCT * FROM film;", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}} @@ -60,7 +60,7 @@ advisor.Rule{Item:"FUN.006", Severity:"L1", Summary:"使用 SUM(COL) 时需注 advisor.Rule{Item:"GRP.001", Severity:"L2", Summary:"不建议对等值查询列使用 GROUP BY", Content:"GROUP BY 中的列在前面的 WHERE 条件中使用了等值查询,对这样的列进行 GROUP BY 意义不大。", Case:"select film_id, title from film where release_year='2006' group by release_year", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}} advisor.Rule{Item:"JOI.001", Severity:"L2", Summary:"JOIN 语句混用逗号和 ANSI 模式", Content:"表连接的时候混用逗号和 ANSI JOIN 不便于人类理解,并且MySQL不同版本的表连接行为和优先级均有所不同,当 MySQL 版本变化后可能会引入错误。", Case:"select c1,c2,c3 from t1,t2 join t3 on t1.c1=t2.c1,t1.c3=t3,c1 where id>1000", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}} advisor.Rule{Item:"JOI.002", Severity:"L4", Summary:"同一张表被连接两次", Content:"相同的表在 FROM 子句中至少出现两次,可以简化为对该表的单次访问。", Case:"select tb1.col from (tb1, tb2) join tb2 on tb1.id=tb.id where tb1.id=1", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}} -advisor.Rule{Item:"JOI.003", Severity:"L4", Summary:"OUTER JOIN 失效", Content:"由于 WHERE 条件错误使得 OUTER JOIN 的外部表无数据返回,这会将查询隐式转换为 INNER JOIN 。如:select c from L left join R using(c) where L.a=5 and R.b=10。这种 SQL 逻辑上可能存在错误或程序员对 OUTER JOIN 如何工作存在误解,因为LEFT/RIGHT JOIN是LEFT/RIGHT OUTER JOIN的缩写。", Case:"select c1,c2,c3 from t1 left outer join t2 using(c1) where t1.c2=2 and t2.c3=4", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}} +advisor.Rule{Item:"JOI.003", Severity:"L4", Summary:"OUTER JOIN 失效", Content:"由于 WHERE 条件错误使得 OUTER JOIN 的外部表无数据返回,这会将查询隐式转换为 INNER JOIN 。如:select c from L left join R using(c) where L.a=5 and R.b=10。这种 SQL 逻辑上可能存在错误或程序员对 OUTER JOIN 如何工作存在误解,因为 LEFT/RIGHT JOIN 是 LEFT/RIGHT OUTER JOIN 的缩写。", Case:"select c1,c2,c3 from t1 left outer join t2 using(c1) where t1.c2=2 and t2.c3=4", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}} advisor.Rule{Item:"JOI.004", Severity:"L4", Summary:"不建议使用排它 JOIN", Content:"只在右侧表为 NULL 的带 WHERE 子句的 LEFT OUTER JOIN 语句,有可能是在WHERE子句中使用错误的列,如:“... FROM l LEFT OUTER JOIN r ON l.l = r.r WHERE r.z IS NULL”,这个查询正确的逻辑可能是 WHERE r.r IS NULL。", Case:"select c1,c2,c3 from t1 left outer join t2 on t1.c1=t2.c1 where t2.c2 is null", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}} advisor.Rule{Item:"JOI.005", Severity:"L2", Summary:"减少 JOIN 的数量", Content:"太多的 JOIN 是复杂的裹脚布式查询的症状。考虑将复杂查询分解成许多简单的查询,并减少 JOIN 的数量。", Case:"select bp1.p_id, b1.d_d as l, b1.b_id from b1 join bp1 on (b1.b_id = bp1.b_id) left outer join (b1 as b2 join bp2 on (b2.b_id = bp2.b_id)) on (bp1.p_id = bp2.p_id ) join bp21 on (b1.b_id = bp1.b_id) join bp31 on (b1.b_id = bp1.b_id) join bp41 on (b1.b_id = bp1.b_id) where b2.b_id = 0", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}} advisor.Rule{Item:"JOI.008", Severity:"L4", Summary:"不要使用跨数据库的 JOIN 查询", Content:"一般来说,跨数据库的 JOIN 查询意味着查询语句跨越了两个不同的子系统,这可能意味着系统耦合度过高或库表结构设计不合理。", Case:"SELECT s,p,d FROM tbl WHERE p.p_id = (SELECT s.p_id FROM tbl WHERE s.c_id = 100996 AND s.q = 1 )", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}} @@ -82,15 +82,15 @@ advisor.Rule{Item:"LIT.002", Severity:"L4", Summary:"日期/时间未使用引 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 {...}} -advisor.Rule{Item:"RES.001", Severity:"L4", Summary:"非确定性的GROUP BY", Content:"SQL返回的列既不在聚合函数中也不是GROUP BY表达式的列中,因此这些值的结果将是非确定性的。如:select a, b, c from tbl where foo=\"bar\" group by a,该SQL返回的结果就是不确定的。", Case:"select c1,c2,c3 from t1 where c2='foo' group by c2", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}} +advisor.Rule{Item:"RES.001", Severity:"L4", Summary:"非确定性的 GROUP BY", Content:"SQL返回的列既不在聚合函数中也不是 GROUP BY 表达式的列中,因此这些值的结果将是非确定性的。如:select a, b, c from tbl where foo=\"bar\" group by a,该 SQL 返回的结果就是不确定的。", Case:"select c1,c2,c3 from t1 where c2='foo' group by c2", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}} advisor.Rule{Item:"RES.002", Severity:"L4", Summary:"未使用 ORDER BY 的 LIMIT 查询", Content:"没有 ORDER BY 的 LIMIT 会导致非确定性的结果,这取决于查询执行计划。", Case:"select col1,col2 from tbl where name=xx limit 10", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}} -advisor.Rule{Item:"RES.003", Severity:"L4", Summary:"UPDATE/DELETE操作使用了LIMIT条件", Content:"UPDATE/DELETE操作使用LIMIT条件和不添加WHERE条件一样危险,它可将会导致主从数据不一致或从库同步中断。", Case:"UPDATE film SET length = 120 WHERE title = 'abc' LIMIT 1;", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}} -advisor.Rule{Item:"RES.004", Severity:"L4", Summary:"UPDATE/DELETE操作指定了ORDER BY条件", Content:"UPDATE/DELETE操作不要指定ORDER BY条件。", Case:"UPDATE film SET length = 120 WHERE title = 'abc' ORDER BY title", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}} -advisor.Rule{Item:"RES.005", Severity:"L4", Summary:"UPDATE可能存在逻辑错误,导致数据损坏", Content:"在一条UPDATE语句中,如果要更新多个字段,字段间不能使用 AND ,而应该用逗号分隔。", Case:"update tbl set col = 1 and cl = 2 where col=3;", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}} +advisor.Rule{Item:"RES.003", Severity:"L4", Summary:"UPDATE/DELETE 操作使用了 LIMIT 条件", Content:"UPDATE/DELETE 操作使用 LIMIT 条件和不添加 WHERE 条件一样危险,它可将会导致主从数据不一致或从库同步中断。", Case:"UPDATE film SET length = 120 WHERE title = 'abc' LIMIT 1;", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}} +advisor.Rule{Item:"RES.004", Severity:"L4", Summary:"UPDATE/DELETE 操作指定了 ORDER BY 条件", Content:"UPDATE/DELETE 操作不要指定 ORDER BY 条件。", Case:"UPDATE film SET length = 120 WHERE title = 'abc' ORDER BY title", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}} +advisor.Rule{Item:"RES.005", Severity:"L4", Summary:"UPDATE 语句可能存在逻辑错误,导致数据损坏", Content:"在一条 UPDATE 语句中,如果要更新多个字段,字段间不能使用 AND ,而应该用逗号分隔。", Case:"update tbl set col = 1 and cl = 2 where col=3;", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}} advisor.Rule{Item:"RES.006", Severity:"L4", Summary:"永远不真的比较条件", Content:"查询条件永远非真,这将导致查询无匹配到的结果。", Case:"select * from tbl where 1 != 1;", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}} advisor.Rule{Item:"RES.007", Severity:"L4", Summary:"永远为真的比较条件", Content:"查询条件永远为真,可能导致 WHERE 条件失效进行全表查询。", Case:"select * from tbl where 1 = 1;", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}} advisor.Rule{Item:"RES.008", Severity:"L2", Summary:"不建议使用LOAD DATA/SELECT ... INTO OUTFILE", Content:"SELECT INTO OUTFILE 需要授予 FILE 权限,这通过会引入安全问题。LOAD DATA 虽然可以提高数据导入速度,但同时也可能导致从库同步延迟过大。", Case:"LOAD DATA INFILE 'data.txt' INTO TABLE db2.my_table;", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}} -advisor.Rule{Item:"SEC.001", Severity:"L0", Summary:"请谨慎使用TRUNCATE操作", Content:"一般来说想清空一张表最快速的做法就是使用TRUNCATE TABLE tbl_name;语句。但TRUNCATE操作也并非是毫无代价的,TRUNCATE TABLE无法返回被删除的准确行数,如果需要返回被删除的行数建议使用DELETE语法。TRUNCATE操作还会重置AUTO_INCREMENT,如果不想重置该值建议使用DELETE FROM tbl_name WHERE 1;替代。TRUNCATE操作会对数据字典添加源数据锁(MDL),当一次需要TRUNCATE很多表时会影响整个实例的所有请求,因此如果要TRUNCATE多个表建议用DROP+CREATE的方式以减少锁时长。", Case:"TRUNCATE TABLE tbl_name", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}} +advisor.Rule{Item:"SEC.001", Severity:"L0", Summary:"请谨慎使用TRUNCATE操作", Content:"一般来说想清空一张表最快速的做法就是使用TRUNCATE TABLE tbl_name;语句。但TRUNCATE操作也并非是毫无代价的,TRUNCATE TABLE无法返回被删除的准确行数,如果需要返回被删除的行数建议使用DELETE语法。TRUNCATE 操作还会重置 AUTO_INCREMENT,如果不想重置该值建议使用 DELETE FROM tbl_name WHERE 1;替代。TRUNCATE 操作会对数据字典添加源数据锁(MDL),当一次需要 TRUNCATE 很多表时会影响整个实例的所有请求,因此如果要 TRUNCATE 多个表建议用 DROP+CREATE 的方式以减少锁时长。", Case:"TRUNCATE TABLE tbl_name", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}} advisor.Rule{Item:"SEC.002", Severity:"L0", Summary:"不使用明文存储密码", Content:"使用明文存储密码或者使用明文在网络上传递密码都是不安全的。如果攻击者能够截获您用来插入密码的SQL语句,他们就能直接读到密码。另外,将用户输入的字符串以明文的形式插入到纯SQL语句中,也会让攻击者发现它。如果您能够读取密码,黑客也可以。解决方案是使用单向哈希函数对原始密码进行加密编码。哈希是指将输入字符串转化成另一个新的、不可识别的字符串的函数。对密码加密表达式加点随机串来防御“字典攻击”。不要将明文密码输入到SQL查询语句中。在应用程序代码中计算哈希串,只在SQL查询中使用哈希串。", Case:"create table test(id int,name varchar(20) not null,password varchar(200)not null)", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}} advisor.Rule{Item:"SEC.003", Severity:"L0", Summary:"使用DELETE/DROP/TRUNCATE等操作时注意备份", Content:"在执行高危操作之前对数据进行备份是十分有必要的。", Case:"delete from table where col = 'condition'", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}} advisor.Rule{Item:"STA.001", Severity:"L0", Summary:"'!=' 运算符是非标准的", Content:"\"<>\"才是标准SQL中的不等于运算符。", Case:"select col1,col2 from tbl where type!=0", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}} diff --git a/doc/heuristic.md b/doc/heuristic.md index 0699a019e2ca10a749af3be9191e8dffd1b07a0f..bf63b17bdd4f840f21a4dfb47c460036465cb189 100644 --- a/doc/heuristic.md +++ b/doc/heuristic.md @@ -506,7 +506,7 @@ CREATE TABLE tab (a INT(1)); * **Item**:COL.017 * **Severity**:L2 -* **Content**:varchar 是可变长字符串,不预先分配存储空间,长度不要超过1024,如果存储长度过长MySQL将定义字段类型为text,独立出来一张表,用主键来对应,避免影响其它字段索引效率。 +* **Content**:varchar 是可变长字符串,不预先分配存储空间,长度不要超过1024,如果存储长度过长 MySQL 将定义字段类型为 text,独立出来一张表,用主键来对应,避免影响其它字段索引效率。 * **Case**: ```sql @@ -636,7 +636,7 @@ select tb1.col from (tb1, tb2) join tb2 on tb1.id=tb.id where tb1.id=1 * **Item**:JOI.003 * **Severity**:L4 -* **Content**:由于 WHERE 条件错误使得 OUTER JOIN 的外部表无数据返回,这会将查询隐式转换为 INNER JOIN 。如:select c from L left join R using(c) where L.a=5 and R.b=10。这种 SQL 逻辑上可能存在错误或程序员对 OUTER JOIN 如何工作存在误解,因为LEFT/RIGHT JOIN是LEFT/RIGHT OUTER JOIN的缩写。 +* **Content**:由于 WHERE 条件错误使得 OUTER JOIN 的外部表无数据返回,这会将查询隐式转换为 INNER JOIN 。如:select c from L left join R using(c) where L.a=5 and R.b=10。这种 SQL 逻辑上可能存在错误或程序员对 OUTER JOIN 如何工作存在误解,因为 LEFT/RIGHT JOIN 是 LEFT/RIGHT OUTER JOIN 的缩写。 * **Case**: ```sql @@ -872,11 +872,11 @@ select c1,c2,c3,c4 from tab1 where col_id REGEXP '[[:<:]]12[[:>:]]' ```sql USE db ``` -## 非确定性的GROUP BY +## 非确定性的 GROUP BY * **Item**:RES.001 * **Severity**:L4 -* **Content**:SQL返回的列既不在聚合函数中也不是GROUP BY表达式的列中,因此这些值的结果将是非确定性的。如:select a, b, c from tbl where foo="bar" group by a,该SQL返回的结果就是不确定的。 +* **Content**:SQL返回的列既不在聚合函数中也不是 GROUP BY 表达式的列中,因此这些值的结果将是非确定性的。如:select a, b, c from tbl where foo="bar" group by a,该 SQL 返回的结果就是不确定的。 * **Case**: ```sql @@ -892,31 +892,31 @@ select c1,c2,c3 from t1 where c2='foo' group by c2 ```sql select col1,col2 from tbl where name=xx limit 10 ``` -## UPDATE/DELETE操作使用了LIMIT条件 +## UPDATE/DELETE 操作使用了 LIMIT 条件 * **Item**:RES.003 * **Severity**:L4 -* **Content**:UPDATE/DELETE操作使用LIMIT条件和不添加WHERE条件一样危险,它可将会导致主从数据不一致或从库同步中断。 +* **Content**:UPDATE/DELETE 操作使用 LIMIT 条件和不添加 WHERE 条件一样危险,它可将会导致主从数据不一致或从库同步中断。 * **Case**: ```sql UPDATE film SET length = 120 WHERE title = 'abc' LIMIT 1; ``` -## UPDATE/DELETE操作指定了ORDER BY条件 +## UPDATE/DELETE 操作指定了 ORDER BY 条件 * **Item**:RES.004 * **Severity**:L4 -* **Content**:UPDATE/DELETE操作不要指定ORDER BY条件。 +* **Content**:UPDATE/DELETE 操作不要指定 ORDER BY 条件。 * **Case**: ```sql UPDATE film SET length = 120 WHERE title = 'abc' ORDER BY title ``` -## UPDATE可能存在逻辑错误,导致数据损坏 +## UPDATE 语句可能存在逻辑错误,导致数据损坏 * **Item**:RES.005 * **Severity**:L4 -* **Content**:在一条UPDATE语句中,如果要更新多个字段,字段间不能使用 AND ,而应该用逗号分隔。 +* **Content**:在一条 UPDATE 语句中,如果要更新多个字段,字段间不能使用 AND ,而应该用逗号分隔。 * **Case**: ```sql @@ -956,7 +956,7 @@ LOAD DATA INFILE 'data.txt' INTO TABLE db2.my_table; * **Item**:SEC.001 * **Severity**:L0 -* **Content**:一般来说想清空一张表最快速的做法就是使用TRUNCATE TABLE tbl\_name;语句。但TRUNCATE操作也并非是毫无代价的,TRUNCATE TABLE无法返回被删除的准确行数,如果需要返回被删除的行数建议使用DELETE语法。TRUNCATE操作还会重置AUTO\_INCREMENT,如果不想重置该值建议使用DELETE FROM tbl\_name WHERE 1;替代。TRUNCATE操作会对数据字典添加源数据锁(MDL),当一次需要TRUNCATE很多表时会影响整个实例的所有请求,因此如果要TRUNCATE多个表建议用DROP+CREATE的方式以减少锁时长。 +* **Content**:一般来说想清空一张表最快速的做法就是使用TRUNCATE TABLE tbl\_name;语句。但TRUNCATE操作也并非是毫无代价的,TRUNCATE TABLE无法返回被删除的准确行数,如果需要返回被删除的行数建议使用DELETE语法。TRUNCATE 操作还会重置 AUTO\_INCREMENT,如果不想重置该值建议使用 DELETE FROM tbl\_name WHERE 1;替代。TRUNCATE 操作会对数据字典添加源数据锁(MDL),当一次需要 TRUNCATE 很多表时会影响整个实例的所有请求,因此如果要 TRUNCATE 多个表建议用 DROP+CREATE 的方式以减少锁时长。 * **Case**: ```sql @@ -1022,11 +1022,11 @@ select col from now where type!=0 ```sql CREATE TABLE ` abc` (a int); ``` -## MySQL对子查询的优化效果不佳 +## MySQL 对子查询的优化效果不佳 * **Item**:SUB.001 * **Severity**:L4 -* **Content**:MySQL将外部查询中的每一行作为依赖子查询执行子查询。 这是导致严重性能问题的常见原因。这可能会在 MySQL 5.6版本中得到改善, 但对于5.1及更早版本, 建议将该类查询分别重写为JOIN或LEFT OUTER JOIN。 +* **Content**:MySQL 将外部查询中的每一行作为依赖子查询执行子查询。 这是导致严重性能问题的常见原因。这可能会在 MySQL 5.6 版本中得到改善, 但对于5.1及更早版本, 建议将该类查询分别重写为 JOIN 或 LEFT OUTER JOIN。 * **Case**: ```sql