提交 e5a73668 编写于 作者: martianzhang's avatar martianzhang

daily update

  1. Makefile add `daily-quick` let test more fast
  2. Heuristic rule word lint
  3. FUN.001 add more logic, unstable now
上级 4768aa4d
...@@ -134,13 +134,13 @@ vendor: vitess tidb ...@@ -134,13 +134,13 @@ vendor: vitess tidb
.PHONY: tidb-parser .PHONY: tidb-parser
tidb-parser: tidb tidb-parser: tidb
@echo "\033[92mimporting tidb sql parser ...\033[0m" @echo "\033[92mimporting tidb sql parser ...\033[0m"
@cd ${GOPATH}/src/github.com/pingcap/tidb && git checkout ec9672cea6612481b1da845dbab620b7a5581ca4 && make parser @cd ${GOPATH}/src/github.com/pingcap/tidb && git checkout --quiet ec9672cea6612481b1da845dbab620b7a5581ca4 && make parser
# gometalinter # gometalinter
# 如果有不想改的lint问题可以使用metalinter.sh加黑名单 # 如果有不想改的lint问题可以使用metalinter.sh加黑名单
#@bash doc/example/metalinter.sh #@bash doc/example/metalinter.sh
.PHONY: lint .PHONY: lint
lint: build lint: fast
@echo "\033[92mRun linter check ...\033[0m" @echo "\033[92mRun linter check ...\033[0m"
CGO_ENABLED=0 retool do gometalinter.v2 -j 1 --config doc/example/metalinter.json ./... CGO_ENABLED=0 retool do gometalinter.v2 -j 1 --config doc/example/metalinter.json ./...
retool do revive -formatter friendly --exclude vendor/... -config doc/example/revive.toml ./... retool do revive -formatter friendly --exclude vendor/... -config doc/example/revive.toml ./...
...@@ -196,6 +196,11 @@ main_test: install ...@@ -196,6 +196,11 @@ main_test: install
daily: | deps fmt vendor tidb-parser docker cover doc lint release install main_test clean logo daily: | deps fmt vendor tidb-parser docker cover doc lint release install main_test clean logo
@echo "\033[92mdaily build finished\033[0m" @echo "\033[92mdaily build finished\033[0m"
# vendor, tidb-parser, docker will cost long time, if all those are ready, daily-quick will much more fast.
.PHONY: daily-quick
daily-quick: | deps fmt cover doc lint logo
@echo "\033[92mdaily-quick build finished\033[0m"
.PHONY: logo .PHONY: logo
logo: logo:
@echo "\033[93m" @echo "\033[93m"
......
...@@ -2181,12 +2181,43 @@ func (q *Query4Audit) RuleDataDrop() Rule { ...@@ -2181,12 +2181,43 @@ func (q *Query4Audit) RuleDataDrop() Rule {
func (q *Query4Audit) RuleCompareWithFunction() Rule { func (q *Query4Audit) RuleCompareWithFunction() Rule {
var rule = q.RuleOK() var rule = q.RuleOK()
err := sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) { err := sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) {
// Vitess 中有些函数进行了单独定义不在 FuncExpr 中,如: substring。所以不能直接用 FuncExpr 判断。
switch n := node.(type) { switch n := node.(type) {
case *sqlparser.ComparisonExpr: case *sqlparser.ComparisonExpr:
switch n.Left.(type) {
case *sqlparser.SQLVal, *sqlparser.ColName:
default:
rule = HeuristicRules["FUN.001"]
return false, nil
}
/*
// func always has bracket
if strings.HasSuffix(sqlparser.String(n.Left), ")") { if strings.HasSuffix(sqlparser.String(n.Left), ")") {
rule = HeuristicRules["FUN.001"] rule = HeuristicRules["FUN.001"]
return false, nil return false, nil
} }
*/
// func(a) between func(c) and func(d)
case *sqlparser.RangeCond:
switch n.Left.(type) {
case *sqlparser.SQLVal, *sqlparser.ColName:
default:
rule = HeuristicRules["FUN.001"]
return false, nil
}
switch n.From.(type) {
case *sqlparser.SQLVal, *sqlparser.ColName:
default:
rule = HeuristicRules["FUN.001"]
return false, nil
}
switch n.To.(type) {
case *sqlparser.SQLVal, *sqlparser.ColName:
default:
rule = HeuristicRules["FUN.001"]
return false, nil
}
} }
return true, nil return true, nil
}, q.Stmt) }, q.Stmt)
......
...@@ -679,7 +679,7 @@ func TestRuleNoDeterministicGroupby(t *testing.T) { ...@@ -679,7 +679,7 @@ func TestRuleNoDeterministicGroupby(t *testing.T) {
func TestRuleNoDeterministicLimit(t *testing.T) { func TestRuleNoDeterministicLimit(t *testing.T) {
common.Log.Debug("Entering function: %s", common.GetFunctionName()) common.Log.Debug("Entering function: %s", common.GetFunctionName())
sqls := []string{ sqls := []string{
"select col1,col2 from tbl where name='zhangsan' limit 10", "select col1,col2 from tbl where name='tony' limit 10",
} }
for _, sql := range sqls { for _, sql := range sqls {
q, err := NewQuery4Audit(sql) q, err := NewQuery4Audit(sql)
...@@ -1194,7 +1194,7 @@ func TestRuleImpreciseDataType(t *testing.T) { ...@@ -1194,7 +1194,7 @@ func TestRuleImpreciseDataType(t *testing.T) {
`select * from tb where col = 0.00001;`, `select * from tb where col = 0.00001;`,
}, },
{ {
"REPLACE INTO `binks3` (`hostname`,`storagehost`, `filename`, `starttime`, `binlogstarttime`, `uploadname`, `binlogsize`, `filesize`, `md5`, `status`) VALUES (1, 1, 1, 1, 1, 1, ?, ?);", "REPLACE INTO `storage` (`hostname`,`storagehost`, `filename`, `starttime`, `binlogstarttime`, `uploadname`, `binlogsize`, `filesize`, `md5`, `status`) VALUES (1, 1, 1, 1, 1, 1, ?, ?);",
}, },
} }
for _, sql := range sqls[0] { for _, sql := range sqls[0] {
...@@ -1249,8 +1249,8 @@ func TestRuleValuesInDefinition(t *testing.T) { ...@@ -1249,8 +1249,8 @@ func TestRuleValuesInDefinition(t *testing.T) {
func TestRuleIndexAttributeOrder(t *testing.T) { func TestRuleIndexAttributeOrder(t *testing.T) {
common.Log.Debug("Entering function: %s", common.GetFunctionName()) common.Log.Debug("Entering function: %s", common.GetFunctionName())
sqls := []string{ sqls := []string{
`create index idx1 on tabl(last_name,first_name);`, `create index idx1 on tab(last_name,first_name);`,
`alter table tabl add index idx1 (last_name,first_name);`, `alter table tab add index idx1 (last_name,first_name);`,
`CREATE TABLE test (id int,blob_col BLOB, INDEX(blob_col(10),id));`, `CREATE TABLE test (id int,blob_col BLOB, INDEX(blob_col(10),id));`,
} }
for _, sql := range sqls { for _, sql := range sqls {
...@@ -1271,7 +1271,7 @@ func TestRuleIndexAttributeOrder(t *testing.T) { ...@@ -1271,7 +1271,7 @@ func TestRuleIndexAttributeOrder(t *testing.T) {
func TestRuleNullUsage(t *testing.T) { func TestRuleNullUsage(t *testing.T) {
common.Log.Debug("Entering function: %s", common.GetFunctionName()) common.Log.Debug("Entering function: %s", common.GetFunctionName())
sqls := []string{ sqls := []string{
`select c1,c2,c3 from tabl where c4 is null or c4 <> 1;`, `select c1,c2,c3 from tab where c4 is null or c4 <> 1;`,
} }
for _, sql := range sqls { for _, sql := range sqls {
q, err := NewQuery4Audit(sql) q, err := NewQuery4Audit(sql)
...@@ -1291,7 +1291,7 @@ func TestRuleNullUsage(t *testing.T) { ...@@ -1291,7 +1291,7 @@ func TestRuleNullUsage(t *testing.T) {
func TestRuleStringConcatenation(t *testing.T) { func TestRuleStringConcatenation(t *testing.T) {
common.Log.Debug("Entering function: %s", common.GetFunctionName()) common.Log.Debug("Entering function: %s", common.GetFunctionName())
sqls := []string{ sqls := []string{
`select c1 || coalesce(' ' || c2 || ' ', ' ') || c3 as c from tabl;`, `select c1 || coalesce(' ' || c2 || ' ', ' ') || c3 as c from tab;`,
} }
for _, sql := range sqls { for _, sql := range sqls {
q, err := NewQuery4Audit(sql) q, err := NewQuery4Audit(sql)
...@@ -1609,7 +1609,7 @@ func TestRuleForbiddenSyntax(t *testing.T) { ...@@ -1609,7 +1609,7 @@ func TestRuleForbiddenSyntax(t *testing.T) {
func TestRuleNestedSubQueries(t *testing.T) { func TestRuleNestedSubQueries(t *testing.T) {
common.Log.Debug("Entering function: %s", common.GetFunctionName()) common.Log.Debug("Entering function: %s", common.GetFunctionName())
sqls := []string{ sqls := []string{
`SELECT s,p,d FROM tabl WHERE p.p_id = (SELECT s.p_id FROM tabl WHERE s.c_id = 100996 AND s.q = 1 );`, `SELECT s,p,d FROM tab WHERE p.p_id = (SELECT s.p_id FROM tab WHERE s.c_id = 100996 AND s.q = 1 );`,
} }
for _, sql := range sqls { for _, sql := range sqls {
q, err := NewQuery4Audit(sql) q, err := NewQuery4Audit(sql)
...@@ -1672,7 +1672,7 @@ func TestRuleMultiDBJoin(t *testing.T) { ...@@ -1672,7 +1672,7 @@ func TestRuleMultiDBJoin(t *testing.T) {
func TestRuleORUsage(t *testing.T) { func TestRuleORUsage(t *testing.T) {
common.Log.Debug("Entering function: %s", common.GetFunctionName()) common.Log.Debug("Entering function: %s", common.GetFunctionName())
sqls := []string{ sqls := []string{
`SELECT c1,c2,c3 FROM tabl WHERE c1 = 14 OR c2 = 17;`, `SELECT c1,c2,c3 FROM tab WHERE c1 = 14 OR c2 = 17;`,
} }
for _, sql := range sqls { for _, sql := range sqls {
q, err := NewQuery4Audit(sql) q, err := NewQuery4Audit(sql)
...@@ -1969,9 +1969,15 @@ func TestRuleDataDrop(t *testing.T) { ...@@ -1969,9 +1969,15 @@ func TestRuleDataDrop(t *testing.T) {
func TestCompareWithFunction(t *testing.T) { func TestCompareWithFunction(t *testing.T) {
common.Log.Debug("Entering function: %s", common.GetFunctionName()) common.Log.Debug("Entering function: %s", common.GetFunctionName())
sqls := [][]string{ sqls := [][]string{
{`select id from t where substring(name,1,3)='abc';`}, {
`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')`,
},
// TODO: 右侧使用函数比较 // TODO: 右侧使用函数比较
{`select id from t where 'abc'=substring(name,1,3);`}, {
`select id from t where 'abc'=substring(name,1,3);`,
`select id from t where col = (select 1)`,
},
} }
for _, sql := range sqls[0] { for _, sql := range sqls[0] {
q, err := NewQuery4Audit(sql) q, err := NewQuery4Audit(sql)
...@@ -2062,7 +2068,7 @@ func TestRuleIn(t *testing.T) { ...@@ -2062,7 +2068,7 @@ func TestRuleIn(t *testing.T) {
} }
// ARG.006 // ARG.006
func TestRuleisNullIsNotNull(t *testing.T) { func TestRuleIsNullIsNotNull(t *testing.T) {
common.Log.Debug("Entering function: %s", common.GetFunctionName()) common.Log.Debug("Entering function: %s", common.GetFunctionName())
sqls := []string{ sqls := []string{
`select id from t where num is null;`, `select id from t where num is null;`,
...@@ -2249,10 +2255,10 @@ func TestRuleAlterDropKey(t *testing.T) { ...@@ -2249,10 +2255,10 @@ func TestRuleAlterDropKey(t *testing.T) {
func TestRuleCantBeNull(t *testing.T) { func TestRuleCantBeNull(t *testing.T) {
common.Log.Debug("Entering function: %s", common.GetFunctionName()) common.Log.Debug("Entering function: %s", common.GetFunctionName())
sqls := []string{ sqls := []string{
"CREATE TABLE `sbtest` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `c` longblob, PRIMARY KEY (`id`));", "CREATE TABLE `tbl` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `c` longblob, PRIMARY KEY (`id`));",
"alter TABLE `sbtest` add column `c` longblob;", "alter TABLE `tbl` add column `c` longblob;",
"alter TABLE `sbtest` add column `c` text;", "alter TABLE `tbl` add column `c` text;",
"alter TABLE `sbtest` add column `c` blob;", "alter TABLE `tbl` add column `c` blob;",
} }
for _, sql := range sqls { for _, sql := range sqls {
q, err := NewQuery4Audit(sql) q, err := NewQuery4Audit(sql)
......
...@@ -212,7 +212,7 @@ func init() { ...@@ -212,7 +212,7 @@ func init() {
Item: "ARG.005", Item: "ARG.005",
Severity: "L1", Severity: "L1",
Summary: "IN要慎用,元素过多会导致全表扫描", Summary: "IN要慎用,元素过多会导致全表扫描",
Content: ` 如:select id from t where num in(1,2,3)对于连续的数值,能用BETWEEN就不要用IN了:select id from t where num between 1 and 3。而当IN值过多时MySQL也可能会进入全表扫描导致性能急剧下降。`, Content: ` 如:select id from t where num in(1,2,3)对于连续的数值,能用 BETWEEN 就不要用 IN 了:select id from t where num between 1 and 3。而当IN值过多时MySQL也可能会进入全表扫描导致性能急剧下降。`,
Case: "select id from t where num in(1,2,3)", Case: "select id from t where num in(1,2,3)",
Func: (*Query4Audit).RuleIn, Func: (*Query4Audit).RuleIn,
}, },
...@@ -228,14 +228,14 @@ func init() { ...@@ -228,14 +228,14 @@ func init() {
Item: "ARG.007", Item: "ARG.007",
Severity: "L3", Severity: "L3",
Summary: "避免使用模式匹配", Summary: "避免使用模式匹配",
Content: `性能问题是使用模式匹配操作符的最大缺点。使用LIKE或正则表达式进行模式匹配进行查询的另一个问题,是可能会返回意料之外的结果。最好的方案就是使用特殊的搜索引擎技术来替代SQL,比如Apache Lucene。另一个可选方案是将结果保存起来从而减少重复的搜索开销。如果一定要使用SQL,请考虑在MySQL中使用像FULLTEXT索引这样的第三方扩展。但更广泛地说,您不一定要使用SQL来解决所有问题。`, Content: `性能问题是使用模式匹配操作符的最大缺点。使用LIKE或正则表达式进行模式匹配进行查询的另一个问题,是可能会返回意料之外的结果。最好的方案就是使用特殊的搜索引擎技术来替代SQL,比如 Apache Lucene。另一个可选方案是将结果保存起来从而减少重复的搜索开销。如果一定要使用SQL,请考虑在 MySQL 中使用像 FULLTEXT 索引这样的第三方扩展。但更广泛地说,您不一定要使用SQL来解决所有问题。`,
Case: "select c_id,c2,c3 from tbl where c2 like 'test%'", Case: "select c_id,c2,c3 from tbl where c2 like 'test%'",
Func: (*Query4Audit).RulePatternMatchingUsage, Func: (*Query4Audit).RulePatternMatchingUsage,
}, },
"ARG.008": { "ARG.008": {
Item: "ARG.008", Item: "ARG.008",
Severity: "L1", Severity: "L1",
Summary: "OR查询索引列时请尽量使用IN谓词", Summary: "OR 查询索引列时请尽量使用 IN 谓词",
Content: `IN-list谓词可以用于索引检索,并且优化器可以对IN-list进行排序,以匹配索引的排序序列,从而获得更有效的检索。请注意,IN-list必须只包含常量,或在查询块执行期间保持常量的值,例如外引用。`, Content: `IN-list谓词可以用于索引检索,并且优化器可以对IN-list进行排序,以匹配索引的排序序列,从而获得更有效的检索。请注意,IN-list必须只包含常量,或在查询块执行期间保持常量的值,例如外引用。`,
Case: "SELECT c1,c2,c3 FROM tbl WHERE c1 = 14 OR c1 = 17", Case: "SELECT c1,c2,c3 FROM tbl WHERE c1 = 14 OR c1 = 17",
Func: (*Query4Audit).RuleORUsage, Func: (*Query4Audit).RuleORUsage,
...@@ -244,7 +244,7 @@ func init() { ...@@ -244,7 +244,7 @@ func init() {
Item: "ARG.009", Item: "ARG.009",
Severity: "L1", Severity: "L1",
Summary: "引号中的字符串开头或结尾包含空格", Summary: "引号中的字符串开头或结尾包含空格",
Content: `如果VARCHAR列的前后存在空格将可能引起逻辑问题,如在MySQL 5.5中'a'和'a '可能会在查询中被认为是相同的值。`, Content: `如果VARCHAR列的前后存在空格将可能引起逻辑问题,如在MySQL 5.5中 'a' 和 'a ' 可能会在查询中被认为是相同的值。`,
Case: "SELECT 'abc '", Case: "SELECT 'abc '",
Func: (*Query4Audit).RuleSpaceWithQuote, Func: (*Query4Audit).RuleSpaceWithQuote,
}, },
...@@ -252,7 +252,7 @@ func init() { ...@@ -252,7 +252,7 @@ func init() {
Item: "ARG.010", Item: "ARG.010",
Severity: "L1", Severity: "L1",
Summary: "不要使用hint,如sql_no_cache, force index, ignore key, straight join等", Summary: "不要使用hint,如sql_no_cache, force index, ignore key, straight join等",
Content: `hint是用来强制SQL按照某个执行计划来执行,但随着数据量变化我们无法保证自己当初的预判是正确的。`, Content: `hint 是用来强制 SQL 按照某个执行计划来执行,但随着数据量变化我们无法保证自己当初的预判是正确的。`,
Case: "SELECT * FROM t1 USE INDEX (i1) ORDER BY a;", Case: "SELECT * FROM t1 USE INDEX (i1) ORDER BY a;",
Func: (*Query4Audit).RuleHint, Func: (*Query4Audit).RuleHint,
}, },
...@@ -569,28 +569,28 @@ func init() { ...@@ -569,28 +569,28 @@ func init() {
Case: "SELECT COUNT(DISTINCT col, col2) FROM tbl;", Case: "SELECT COUNT(DISTINCT col, col2) FROM tbl;",
Func: (*Query4Audit).RuleCountDistinctMultiCol, Func: (*Query4Audit).RuleCountDistinctMultiCol,
}, },
// DIS.003灵感来源于如下链接 // DIS.003 灵感来源于如下链接
// http://www.ijstr.org/final-print/oct2015/Query-Optimization-Techniques-Tips-For-Writing-Efficient-And-Faster-Sql-Queries.pdf // http://www.ijstr.org/final-print/oct2015/Query-Optimization-Techniques-Tips-For-Writing-Efficient-And-Faster-Sql-Queries.pdf
"DIS.003": { "DIS.003": {
Item: "DIS.003", Item: "DIS.003",
Severity: "L3", Severity: "L3",
Summary: "DISTINCT *对有主键的表没有意义", Summary: "DISTINCT *对有主键的表没有意义",
Content: `当表已经有主键时,对所有列进行DISTINCT的输出结果与不进行DISTINCT操作的结果相同,请不要画蛇添足。`, Content: `当表已经有主键时,对所有列进行 DISTINCT 的输出结果与不进行 DISTINCT 操作的结果相同,请不要画蛇添足。`,
Case: "SELECT DISTINCT * FROM film;", Case: "SELECT DISTINCT * FROM film;",
Func: (*Query4Audit).RuleDistinctStar, Func: (*Query4Audit).RuleDistinctStar,
}, },
"FUN.001": { "FUN.001": {
Item: "FUN.001", Item: "FUN.001",
Severity: "L2", Severity: "L2",
Summary: "避免在WHERE条件中使用函数或其他运算符", Summary: "避免在 WHERE 条件中使用函数或其他运算符",
Content: `虽然在SQL中使用函数可以简化很多复杂的查询,但使用了函数的查询无法利用表中已经建立的索引,该查询将会是全表扫描,性能较差。通常建议将列名写在比较运算符左侧,将查询过滤条件放在比较运算符右侧。`, Content: `虽然在 SQL 中使用函数可以简化很多复杂的查询,但使用了函数的查询无法利用表中已经建立的索引,该查询将会是全表扫描,性能较差。通常建议将列名写在比较运算符左侧,将查询过滤条件放在比较运算符右侧。也不建议在查询比较条件两侧书写多余的括号,这会对阅读产生比较大的困扰。`,
Case: "select id from t where substring(name,1,3)='abc'", Case: "select id from t where substring(name,1,3)='abc'",
Func: (*Query4Audit).RuleCompareWithFunction, Func: (*Query4Audit).RuleCompareWithFunction,
}, },
"FUN.002": { "FUN.002": {
Item: "FUN.002", Item: "FUN.002",
Severity: "L1", Severity: "L1",
Summary: "指定了WHERE条件或非MyISAM引擎时使用COUNT(*)操作性能不佳", Summary: "指定了 WHERE 条件或非 MyISAM 引擎时使用 COUNT(*) 操作性能不佳",
Content: `COUNT(*)的作用是统计表行数,COUNT(COL)的作用是统计指定列非NULL的行数。MyISAM表对于COUNT(*)统计全表行数进行了特殊的优化,通常情况下非常快。但对于非MyISAM表或指定了某些WHERE条件,COUNT(*)操作需要扫描大量的行才能获取精确的结果,性能也因此不佳。有时候某些业务场景并不需要完全精确的COUNT值,此时可以用近似值来代替。EXPLAIN出来的优化器估算的行数就是一个不错的近似值,执行EXPLAIN并不需要真正去执行查询,所以成本很低。`, Content: `COUNT(*)的作用是统计表行数,COUNT(COL)的作用是统计指定列非NULL的行数。MyISAM表对于COUNT(*)统计全表行数进行了特殊的优化,通常情况下非常快。但对于非MyISAM表或指定了某些WHERE条件,COUNT(*)操作需要扫描大量的行才能获取精确的结果,性能也因此不佳。有时候某些业务场景并不需要完全精确的COUNT值,此时可以用近似值来代替。EXPLAIN出来的优化器估算的行数就是一个不错的近似值,执行EXPLAIN并不需要真正去执行查询,所以成本很低。`,
Case: "SELECT c3, COUNT(*) AS accounts FROM tab where c2 < 10000 GROUP BY c3 ORDER BY num", Case: "SELECT c3, COUNT(*) AS accounts FROM tab where c2 < 10000 GROUP BY c3 ORDER BY num",
Func: (*Query4Audit).RuleCountStar, Func: (*Query4Audit).RuleCountStar,
...@@ -631,14 +631,14 @@ func init() { ...@@ -631,14 +631,14 @@ func init() {
Item: "GRP.001", Item: "GRP.001",
Severity: "L2", Severity: "L2",
Summary: "不建议对等值查询列使用GROUP BY", Summary: "不建议对等值查询列使用GROUP BY",
Content: `GROUP BY中的列在前面的WHERE条件中使用了等值查询,对这样的列进行GROUP BY意义不大。`, Content: `GROUP BY 中的列在前面的 WHERE 条件中使用了等值查询,对这样的列进行GROUP BY意义不大。`,
Case: "select film_id, title from film where release_year='2006' group by release_year", Case: "select film_id, title from film where release_year='2006' group by release_year",
Func: (*Query4Audit).RuleOK, // 该建议在indexAdvisor中给 Func: (*Query4Audit).RuleOK, // 该建议在indexAdvisor中给
}, },
"JOI.001": { "JOI.001": {
Item: "JOI.001", Item: "JOI.001",
Severity: "L2", Severity: "L2",
Summary: "JOIN语句混用逗号和ANSI模式", Summary: "JOIN 语句混用逗号和 ANSI 模式",
Content: `表连接的时候混用逗号和ANSI JOIN不便于人类理解,并且MySQL不同版本的表连接行为和优先级均有所不同,当MySQL版本变化后可能会引入错误。`, 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", Case: "select c1,c2,c3 from t1,t2 join t3 on t1.c1=t2.c1,t1.c3=t3,c1 where id>1000",
Func: (*Query4Audit).RuleCommaAnsiJoin, Func: (*Query4Audit).RuleCommaAnsiJoin,
...@@ -655,7 +655,7 @@ func init() { ...@@ -655,7 +655,7 @@ func init() {
Item: "JOI.003", Item: "JOI.003",
Severity: "L4", Severity: "L4",
Summary: "OUTER JOIN失效", 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", 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 Func: (*Query4Audit).RuleOK, // TODO
}, },
...@@ -663,7 +663,7 @@ func init() { ...@@ -663,7 +663,7 @@ func init() {
Item: "JOI.004", Item: "JOI.004",
Severity: "L4", Severity: "L4",
Summary: "不建议使用排它JOIN", 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。`, 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", Case: "select c1,c2,c3 from t1 left outer join t2 on t1.c1=t2.c1 where t2.c2 is null",
Func: (*Query4Audit).RuleOK, // TODO Func: (*Query4Audit).RuleOK, // TODO
}, },
...@@ -679,7 +679,7 @@ func init() { ...@@ -679,7 +679,7 @@ func init() {
Item: "JOI.006", Item: "JOI.006",
Severity: "L4", Severity: "L4",
Summary: "将嵌套查询重写为JOIN通常会导致更高效的执行和更有效的优化", Summary: "将嵌套查询重写为JOIN通常会导致更高效的执行和更有效的优化",
Content: `一般来说,非嵌套子查询总是用于关联子查询,最多是来自FROM子句中的一个表,这些子查询用于ANY、ALL和EXISTS的谓词。如果可以根据查询语义决定子查询最多返回一个行,那么一个不相关的子查询或来自FROM子句中的多个表的子查询就被压平了。`, Content: `一般来说,非嵌套子查询总是用于关联子查询,最多是来自FROM子句中的一个表,这些子查询用于 ANY, ALL 和 EXISTS 的谓词。如果可以根据查询语义决定子查询最多返回一个行,那么一个不相关的子查询或来自FROM子句中的多个表的子查询就被压平了。`,
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 )", 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 )",
Func: (*Query4Audit).RuleNestedSubQueries, Func: (*Query4Audit).RuleNestedSubQueries,
}, },
...@@ -694,8 +694,8 @@ func init() { ...@@ -694,8 +694,8 @@ func init() {
"JOI.008": { "JOI.008": {
Item: "JOI.008", Item: "JOI.008",
Severity: "L4", Severity: "L4",
Summary: "不要使用跨DB的Join查询", Summary: "不要使用跨数据库的 JOIN 查询",
Content: `一般来说,跨DB的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 )", 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 )",
Func: (*Query4Audit).RuleMultiDBJoin, Func: (*Query4Audit).RuleMultiDBJoin,
}, },
...@@ -752,8 +752,8 @@ func init() { ...@@ -752,8 +752,8 @@ func init() {
"KEY.007": { "KEY.007": {
Item: "KEY.007", Item: "KEY.007",
Severity: "L4", Severity: "L4",
Summary: "未指定主键或主键非int或bigint", Summary: "未指定主键或主键非 int 或 bigint",
Content: `未指定主键或主键非int或bigint,建议将主键设置为int unsigned或bigint unsigned。`, Content: `未指定主键或主键非 int 或 bigint,建议将主键设置为 int unsigned 或 bigint unsigned。`,
Case: "CREATE TABLE tbl (a int);", Case: "CREATE TABLE tbl (a int);",
Func: (*Query4Audit).RulePKNotInt, Func: (*Query4Audit).RulePKNotInt,
}, },
...@@ -776,7 +776,7 @@ func init() { ...@@ -776,7 +776,7 @@ func init() {
"KWR.001": { "KWR.001": {
Item: "KWR.001", Item: "KWR.001",
Severity: "L2", Severity: "L2",
Summary: "SQL_CALC_FOUND_ROWS效率低下", Summary: "SQL_CALC_FOUND_ROWS 效率低下",
Content: `因为SQL_CALC_FOUND_ROWS不能很好地扩展,所以可能导致性能问题; 建议业务使用其他策略来替代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", Case: "select SQL_CALC_FOUND_ROWS col from tbl where id>1000",
Func: (*Query4Audit).RuleSQLCalcFoundRows, Func: (*Query4Audit).RuleSQLCalcFoundRows,
...@@ -841,7 +841,7 @@ func init() { ...@@ -841,7 +841,7 @@ func init() {
Item: "LIT.004", Item: "LIT.004",
Severity: "L1", Severity: "L1",
Summary: "请使用分号或已设定的DELIMITER结尾", Summary: "请使用分号或已设定的DELIMITER结尾",
Content: `USE database, SHOW DATABASES等命令也需要使用使用分号或已设定的DELIMITER结尾。`, Content: `USE database, SHOW DATABASES 等命令也需要使用使用分号或已设定的 DELIMITER 结尾。`,
Case: "USE db", Case: "USE db",
Func: (*Query4Audit).RuleOK, // TODO: RuleAddDelimiter Func: (*Query4Audit).RuleOK, // TODO: RuleAddDelimiter
}, },
...@@ -856,8 +856,8 @@ func init() { ...@@ -856,8 +856,8 @@ func init() {
"RES.002": { "RES.002": {
Item: "RES.002", Item: "RES.002",
Severity: "L4", Severity: "L4",
Summary: "未使用ORDER BY的LIMIT查询", Summary: "未使用 ORDER BY 的 LIMIT 查询",
Content: `没有ORDER BY的LIMIT会导致非确定性的结果,这取决于查询执行计划。`, Content: `没有 ORDER BY 的 LIMIT 会导致非确定性的结果,这取决于查询执行计划。`,
Case: "select col1,col2 from tbl where name=xx limit 10", Case: "select col1,col2 from tbl where name=xx limit 10",
Func: (*Query4Audit).RuleNoDeterministicLimit, Func: (*Query4Audit).RuleNoDeterministicLimit,
}, },
...@@ -897,7 +897,7 @@ func init() { ...@@ -897,7 +897,7 @@ func init() {
Item: "RES.007", Item: "RES.007",
Severity: "L4", Severity: "L4",
Summary: "永远为真的比较条件", Summary: "永远为真的比较条件",
Content: "查询条件永远为真,这将导致WHERE条件失效进行全表查询。", Content: "查询条件永远为真,可能导致 WHERE 条件失效进行全表查询。",
Case: "select * from tbl where 1 = 1;", Case: "select * from tbl where 1 = 1;",
Func: (*Query4Audit).RuleMeaninglessWhere, Func: (*Query4Audit).RuleMeaninglessWhere,
}, },
...@@ -905,7 +905,7 @@ func init() { ...@@ -905,7 +905,7 @@ func init() {
Item: "RES.008", Item: "RES.008",
Severity: "L2", Severity: "L2",
Summary: "不建议使用LOAD DATA/SELECT ... INTO OUTFILE", Summary: "不建议使用LOAD DATA/SELECT ... INTO OUTFILE",
Content: "SELECT INTO OUTFILE需要授予FILE权限,这通过会引入安全问题。LOAD DATA虽然可以提高数据导入速度,但同时也可能导致从库同步延迟过大。", Content: "SELECT INTO OUTFILE 需要授予 FILE 权限,这通过会引入安全问题。LOAD DATA 虽然可以提高数据导入速度,但同时也可能导致从库同步延迟过大。",
Case: "LOAD DATA INFILE 'data.txt' INTO TABLE db2.my_table;", Case: "LOAD DATA INFILE 'data.txt' INTO TABLE db2.my_table;",
Func: (*Query4Audit).RuleLoadFile, Func: (*Query4Audit).RuleLoadFile,
}, },
...@@ -945,7 +945,7 @@ func init() { ...@@ -945,7 +945,7 @@ func init() {
Item: "STA.002", Item: "STA.002",
Severity: "L1", Severity: "L1",
Summary: "库名或表名点后建议不要加空格", Summary: "库名或表名点后建议不要加空格",
Content: `当使用db.table或table.column格式访问表或字段时,请不要在点号后面添加空格,虽然这样语法正确。`, Content: `当使用 db.table 或 table.column 格式访问表或字段时,请不要在点号后面添加空格,虽然这样语法正确。`,
Case: "select col from sakila. film", Case: "select col from sakila. film",
Func: (*Query4Audit).RuleSpaceAfterDot, Func: (*Query4Audit).RuleSpaceAfterDot,
}, },
...@@ -976,7 +976,7 @@ func init() { ...@@ -976,7 +976,7 @@ func init() {
"SUB.002": { "SUB.002": {
Item: "SUB.002", Item: "SUB.002",
Severity: "L2", Severity: "L2",
Summary: "如果您不在乎重复的话,建议使用UNION ALL替代UNION", Summary: "如果您不在乎重复的话,建议使用 UNION ALL 替代 UNION",
Content: `与去除重复的UNION不同,UNION ALL允许重复元组。如果您不关心重复元组,那么使用UNION ALL将是一个更快的选项。`, Content: `与去除重复的UNION不同,UNION ALL允许重复元组。如果您不关心重复元组,那么使用UNION ALL将是一个更快的选项。`,
Case: "select teacher_id as id,people_name as name from t1,t2 where t1.teacher_id=t2.people_id union select student_id as id,people_name as name from t1,t2 where t1.student_id=t2.people_id", Case: "select teacher_id as id,people_name as name from t1,t2 where t1.teacher_id=t2.people_id union select student_id as id,people_name as name from t1,t2 where t1.student_id=t2.people_id",
Func: (*Query4Audit).RuleUNIONUsage, Func: (*Query4Audit).RuleUNIONUsage,
...@@ -984,8 +984,8 @@ func init() { ...@@ -984,8 +984,8 @@ func init() {
"SUB.003": { "SUB.003": {
Item: "SUB.003", Item: "SUB.003",
Severity: "L3", Severity: "L3",
Summary: "考虑使用EXISTS而不是DISTINCT子查询", Summary: "考虑使用 EXISTS 而不是 DISTINCT 子查询",
Content: `DISTINCT关键字在对元组排序后删除重复。相反,考虑使用一个带有EXISTS关键字的子查询,您可以避免返回整个表。`, Content: `DISTINCT 关键字在对元组排序后删除重复。相反,考虑使用一个带有 EXISTS 关键字的子查询,您可以避免返回整个表。`,
Case: "SELECT DISTINCT c.c_id, c.c_name FROM c,e WHERE e.c_id = c.c_id", Case: "SELECT DISTINCT c.c_id, c.c_name FROM c,e WHERE e.c_id = c.c_id",
Func: (*Query4Audit).RuleDistinctJoinUsage, Func: (*Query4Audit).RuleDistinctJoinUsage,
}, },
...@@ -1005,7 +1005,7 @@ func init() { ...@@ -1005,7 +1005,7 @@ func init() {
Item: "SUB.005", Item: "SUB.005",
Severity: "L8", Severity: "L8",
Summary: "子查询不支持LIMIT", Summary: "子查询不支持LIMIT",
Content: `当前MySQL版本不支持在子查询中进行'LIMIT & IN/ALL/ANY/SOME'。`, Content: `当前 MySQL 版本不支持在子查询中进行 'LIMIT & IN/ALL/ANY/SOME'。`,
Case: "SELECT * FROM staff WHERE name IN (SELECT NAME FROM customer ORDER BY name LIMIT 1)", Case: "SELECT * FROM staff WHERE name IN (SELECT NAME FROM customer ORDER BY name LIMIT 1)",
Func: (*Query4Audit).RuleSubQueryLimit, Func: (*Query4Audit).RuleSubQueryLimit,
}, },
......
...@@ -116,7 +116,7 @@ SELECT * FROM tb WHERE col IN (NULL); ...@@ -116,7 +116,7 @@ SELECT * FROM tb WHERE col IN (NULL);
* **Item**:ARG.005 * **Item**:ARG.005
* **Severity**:L1 * **Severity**:L1
* **Content**: 如:select id from t where num in(1,2,3)对于连续的数值,能用BETWEEN就不要用IN了:select id from t where num between 1 and 3。而当IN值过多时MySQL也可能会进入全表扫描导致性能急剧下降。 * **Content**: 如:select id from t where num in(1,2,3)对于连续的数值,能用 BETWEEN 就不要用 IN 了:select id from t where num between 1 and 3。而当IN值过多时MySQL也可能会进入全表扫描导致性能急剧下降。
* **Case**: * **Case**:
```sql ```sql
...@@ -136,13 +136,13 @@ select id from t where num is null ...@@ -136,13 +136,13 @@ select id from t where num is null
* **Item**:ARG.007 * **Item**:ARG.007
* **Severity**:L3 * **Severity**:L3
* **Content**:性能问题是使用模式匹配操作符的最大缺点。使用LIKE或正则表达式进行模式匹配进行查询的另一个问题,是可能会返回意料之外的结果。最好的方案就是使用特殊的搜索引擎技术来替代SQL,比如Apache Lucene。另一个可选方案是将结果保存起来从而减少重复的搜索开销。如果一定要使用SQL,请考虑在MySQL中使用像FULLTEXT索引这样的第三方扩展。但更广泛地说,您不一定要使用SQL来解决所有问题。 * **Content**:性能问题是使用模式匹配操作符的最大缺点。使用LIKE或正则表达式进行模式匹配进行查询的另一个问题,是可能会返回意料之外的结果。最好的方案就是使用特殊的搜索引擎技术来替代SQL,比如 Apache Lucene。另一个可选方案是将结果保存起来从而减少重复的搜索开销。如果一定要使用SQL,请考虑在 MySQL 中使用像 FULLTEXT 索引这样的第三方扩展。但更广泛地说,您不一定要使用SQL来解决所有问题。
* **Case**: * **Case**:
```sql ```sql
select c_id,c2,c3 from tbl where c2 like 'test%' select c_id,c2,c3 from tbl where c2 like 'test%'
``` ```
## OR查询索引列时请尽量使用IN谓词 ## OR 查询索引列时请尽量使用 IN 谓词
* **Item**:ARG.008 * **Item**:ARG.008
* **Severity**:L1 * **Severity**:L1
...@@ -156,7 +156,7 @@ SELECT c1,c2,c3 FROM tbl WHERE c1 = 14 OR c1 = 17 ...@@ -156,7 +156,7 @@ SELECT c1,c2,c3 FROM tbl WHERE c1 = 14 OR c1 = 17
* **Item**:ARG.009 * **Item**:ARG.009
* **Severity**:L1 * **Severity**:L1
* **Content**:如果VARCHAR列的前后存在空格将可能引起逻辑问题,如在MySQL 5.5中'a'和'a '可能会在查询中被认为是相同的值。 * **Content**:如果VARCHAR列的前后存在空格将可能引起逻辑问题,如在MySQL 5.5中 'a' 和 'a ' 可能会在查询中被认为是相同的值。
* **Case**: * **Case**:
```sql ```sql
...@@ -166,7 +166,7 @@ SELECT 'abc ' ...@@ -166,7 +166,7 @@ SELECT 'abc '
* **Item**:ARG.010 * **Item**:ARG.010
* **Severity**:L1 * **Severity**:L1
* **Content**:hint是用来强制SQL按照某个执行计划来执行,但随着数据量变化我们无法保证自己当初的预判是正确的。 * **Content**:hint 是用来强制 SQL 按照某个执行计划来执行,但随着数据量变化我们无法保证自己当初的预判是正确的。
* **Case**: * **Case**:
```sql ```sql
...@@ -536,23 +536,23 @@ SELECT COUNT(DISTINCT col, col2) FROM tbl; ...@@ -536,23 +536,23 @@ SELECT COUNT(DISTINCT col, col2) FROM tbl;
* **Item**:DIS.003 * **Item**:DIS.003
* **Severity**:L3 * **Severity**:L3
* **Content**:当表已经有主键时,对所有列进行DISTINCT的输出结果与不进行DISTINCT操作的结果相同,请不要画蛇添足。 * **Content**:当表已经有主键时,对所有列进行 DISTINCT 的输出结果与不进行 DISTINCT 操作的结果相同,请不要画蛇添足。
* **Case**: * **Case**:
```sql ```sql
SELECT DISTINCT * FROM film; SELECT DISTINCT * FROM film;
``` ```
## 避免在WHERE条件中使用函数或其他运算符 ## 避免在 WHERE 条件中使用函数或其他运算符
* **Item**:FUN.001 * **Item**:FUN.001
* **Severity**:L2 * **Severity**:L2
* **Content**:虽然在SQL中使用函数可以简化很多复杂的查询,但使用了函数的查询无法利用表中已经建立的索引,该查询将会是全表扫描,性能较差。通常建议将列名写在比较运算符左侧,将查询过滤条件放在比较运算符右侧 * **Content**:虽然在 SQL 中使用函数可以简化很多复杂的查询,但使用了函数的查询无法利用表中已经建立的索引,该查询将会是全表扫描,性能较差。通常建议将列名写在比较运算符左侧,将查询过滤条件放在比较运算符右侧。也不建议在查询比较条件两侧书写多余的括号,这会对阅读产生比较大的困扰
* **Case**: * **Case**:
```sql ```sql
select id from t where substring(name,1,3)='abc' select id from t where substring(name,1,3)='abc'
``` ```
## 指定了WHERE条件或非MyISAM引擎时使用COUNT(\*)操作性能不佳 ## 指定了 WHERE 条件或非 MyISAM 引擎时使用 COUNT(\*) 操作性能不佳
* **Item**:FUN.002 * **Item**:FUN.002
* **Severity**:L1 * **Severity**:L1
...@@ -606,13 +606,13 @@ SELECT SUM(COL) FROM tbl; ...@@ -606,13 +606,13 @@ SELECT SUM(COL) FROM tbl;
* **Item**:GRP.001 * **Item**:GRP.001
* **Severity**:L2 * **Severity**:L2
* **Content**:GROUP BY中的列在前面的WHERE条件中使用了等值查询,对这样的列进行GROUP BY意义不大。 * **Content**:GROUP BY 中的列在前面的 WHERE 条件中使用了等值查询,对这样的列进行GROUP BY意义不大。
* **Case**: * **Case**:
```sql ```sql
select film_id, title from film where release_year='2006' group by release_year select film_id, title from film where release_year='2006' group by release_year
``` ```
## JOIN语句混用逗号和ANSI模式 ## JOIN 语句混用逗号和 ANSI 模式
* **Item**:JOI.001 * **Item**:JOI.001
* **Severity**:L2 * **Severity**:L2
...@@ -636,7 +636,7 @@ select tb1.col from (tb1, tb2) join tb2 on tb1.id=tb.id where tb1.id=1 ...@@ -636,7 +636,7 @@ select tb1.col from (tb1, tb2) join tb2 on tb1.id=tb.id where tb1.id=1
* **Item**:JOI.003 * **Item**:JOI.003
* **Severity**:L4 * **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**: * **Case**:
```sql ```sql
...@@ -646,7 +646,7 @@ select c1,c2,c3 from t1 left outer join t2 using(c1) where t1.c2=2 and t2.c3=4 ...@@ -646,7 +646,7 @@ select c1,c2,c3 from t1 left outer join t2 using(c1) where t1.c2=2 and t2.c3=4
* **Item**:JOI.004 * **Item**:JOI.004
* **Severity**:L4 * **Severity**:L4
* **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。 * **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**: * **Case**:
```sql ```sql
...@@ -666,7 +666,7 @@ select bp1.p_id, b1.d_d as l, b1.b_id from b1 join bp1 on (b1.b_id = bp1.b_id) l ...@@ -666,7 +666,7 @@ select bp1.p_id, b1.d_d as l, b1.b_id from b1 join bp1 on (b1.b_id = bp1.b_id) l
* **Item**:JOI.006 * **Item**:JOI.006
* **Severity**:L4 * **Severity**:L4
* **Content**:一般来说,非嵌套子查询总是用于关联子查询,最多是来自FROM子句中的一个表,这些子查询用于ANY、ALL和EXISTS的谓词。如果可以根据查询语义决定子查询最多返回一个行,那么一个不相关的子查询或来自FROM子句中的多个表的子查询就被压平了。 * **Content**:一般来说,非嵌套子查询总是用于关联子查询,最多是来自FROM子句中的一个表,这些子查询用于 ANY, ALL 和 EXISTS 的谓词。如果可以根据查询语义决定子查询最多返回一个行,那么一个不相关的子查询或来自FROM子句中的多个表的子查询就被压平了。
* **Case**: * **Case**:
```sql ```sql
...@@ -682,11 +682,11 @@ SELECT s,p,d FROM tbl WHERE p.p_id = (SELECT s.p_id FROM tbl WHERE s.c_id = 1009 ...@@ -682,11 +682,11 @@ SELECT s,p,d FROM tbl WHERE p.p_id = (SELECT s.p_id FROM tbl WHERE s.c_id = 1009
```sql ```sql
UPDATE users u LEFT JOIN hobby h ON u.id = h.uid SET u.name = 'pianoboy' WHERE h.hobby = 'piano'; UPDATE users u LEFT JOIN hobby h ON u.id = h.uid SET u.name = 'pianoboy' WHERE h.hobby = 'piano';
``` ```
## 不要使用跨DB的Join查询 ## 不要使用跨数据库的 JOIN 查询
* **Item**:JOI.008 * **Item**:JOI.008
* **Severity**:L4 * **Severity**:L4
* **Content**:一般来说,跨DB的Join查询意味着查询语句跨越了两个不同的子系统,这可能意味着系统耦合度过高或库表结构设计不合理。 * **Content**:一般来说,跨数据库的 JOIN 查询意味着查询语句跨越了两个不同的子系统,这可能意味着系统耦合度过高或库表结构设计不合理。
* **Case**: * **Case**:
```sql ```sql
...@@ -752,11 +752,11 @@ CREATE TABLE tbl ( a int, b int, c int, KEY idx_a (`a`),KEY idx_b(`b`),KEY idx_c ...@@ -752,11 +752,11 @@ CREATE TABLE tbl ( a int, b int, c int, KEY idx_a (`a`),KEY idx_b(`b`),KEY idx_c
```sql ```sql
CREATE TABLE tbl ( a int, b int, c int, PRIMARY KEY(`a`,`b`,`c`)); CREATE TABLE tbl ( a int, b int, c int, PRIMARY KEY(`a`,`b`,`c`));
``` ```
## 未指定主键或主键非int或bigint ## 未指定主键或主键非 int 或 bigint
* **Item**:KEY.007 * **Item**:KEY.007
* **Severity**:L4 * **Severity**:L4
* **Content**:未指定主键或主键非int或bigint,建议将主键设置为int unsigned或bigint unsigned。 * **Content**:未指定主键或主键非 int 或 bigint,建议将主键设置为 int unsigned 或 bigint unsigned。
* **Case**: * **Case**:
```sql ```sql
...@@ -782,7 +782,7 @@ SELECT * FROM tbl ORDER BY a DESC, b ASC; ...@@ -782,7 +782,7 @@ SELECT * FROM tbl ORDER BY a DESC, b ASC;
```sql ```sql
CREATE UNIQUE INDEX part_of_name ON customer (name(10)); CREATE UNIQUE INDEX part_of_name ON customer (name(10));
``` ```
## SQL\_CALC\_FOUND\_ROWS效率低下 ## SQL\_CALC\_FOUND\_ROWS 效率低下
* **Item**:KWR.001 * **Item**:KWR.001
* **Severity**:L2 * **Severity**:L2
...@@ -866,7 +866,7 @@ select c1,c2,c3,c4 from tab1 where col_id REGEXP '[[:<:]]12[[:>:]]' ...@@ -866,7 +866,7 @@ select c1,c2,c3,c4 from tab1 where col_id REGEXP '[[:<:]]12[[:>:]]'
* **Item**:LIT.004 * **Item**:LIT.004
* **Severity**:L1 * **Severity**:L1
* **Content**:USE database, SHOW DATABASES等命令也需要使用使用分号或已设定的DELIMITER结尾。 * **Content**:USE database, SHOW DATABASES 等命令也需要使用使用分号或已设定的 DELIMITER 结尾。
* **Case**: * **Case**:
```sql ```sql
...@@ -882,11 +882,11 @@ USE db ...@@ -882,11 +882,11 @@ USE db
```sql ```sql
select c1,c2,c3 from t1 where c2='foo' group by c2 select c1,c2,c3 from t1 where c2='foo' group by c2
``` ```
## 未使用ORDER BY的LIMIT查询 ## 未使用 ORDER BY 的 LIMIT 查询
* **Item**:RES.002 * **Item**:RES.002
* **Severity**:L4 * **Severity**:L4
* **Content**:没有ORDER BY的LIMIT会导致非确定性的结果,这取决于查询执行计划。 * **Content**:没有 ORDER BY 的 LIMIT 会导致非确定性的结果,这取决于查询执行计划。
* **Case**: * **Case**:
```sql ```sql
...@@ -936,7 +936,7 @@ select * from tbl where 1 != 1; ...@@ -936,7 +936,7 @@ select * from tbl where 1 != 1;
* **Item**:RES.007 * **Item**:RES.007
* **Severity**:L4 * **Severity**:L4
* **Content**:查询条件永远为真,这将导致WHERE条件失效进行全表查询。 * **Content**:查询条件永远为真,可能导致 WHERE 条件失效进行全表查询。
* **Case**: * **Case**:
```sql ```sql
...@@ -946,7 +946,7 @@ select * from tbl where 1 = 1; ...@@ -946,7 +946,7 @@ select * from tbl where 1 = 1;
* **Item**:RES.008 * **Item**:RES.008
* **Severity**:L2 * **Severity**:L2
* **Content**:SELECT INTO OUTFILE需要授予FILE权限,这通过会引入安全问题。LOAD DATA虽然可以提高数据导入速度,但同时也可能导致从库同步延迟过大。 * **Content**:SELECT INTO OUTFILE 需要授予 FILE 权限,这通过会引入安全问题。LOAD DATA 虽然可以提高数据导入速度,但同时也可能导致从库同步延迟过大。
* **Case**: * **Case**:
```sql ```sql
...@@ -996,7 +996,7 @@ select col1,col2 from tbl where type!=0 ...@@ -996,7 +996,7 @@ select col1,col2 from tbl where type!=0
* **Item**:STA.002 * **Item**:STA.002
* **Severity**:L1 * **Severity**:L1
* **Content**:当使用db.table或table.column格式访问表或字段时,请不要在点号后面添加空格,虽然这样语法正确。 * **Content**:当使用 db.table 或 table.column 格式访问表或字段时,请不要在点号后面添加空格,虽然这样语法正确。
* **Case**: * **Case**:
```sql ```sql
...@@ -1032,7 +1032,7 @@ CREATE TABLE ` abc` (a int); ...@@ -1032,7 +1032,7 @@ CREATE TABLE ` abc` (a int);
```sql ```sql
select col1,col2,col3 from table1 where col2 in(select col from table2) select col1,col2,col3 from table1 where col2 in(select col from table2)
``` ```
## 如果您不在乎重复的话,建议使用UNION ALL替代UNION ## 如果您不在乎重复的话,建议使用 UNION ALL 替代 UNION
* **Item**:SUB.002 * **Item**:SUB.002
* **Severity**:L2 * **Severity**:L2
...@@ -1042,11 +1042,11 @@ select col1,col2,col3 from table1 where col2 in(select col from table2) ...@@ -1042,11 +1042,11 @@ select col1,col2,col3 from table1 where col2 in(select col from table2)
```sql ```sql
select teacher_id as id,people_name as name from t1,t2 where t1.teacher_id=t2.people_id union select student_id as id,people_name as name from t1,t2 where t1.student_id=t2.people_id select teacher_id as id,people_name as name from t1,t2 where t1.teacher_id=t2.people_id union select student_id as id,people_name as name from t1,t2 where t1.student_id=t2.people_id
``` ```
## 考虑使用EXISTS而不是DISTINCT子查询 ## 考虑使用 EXISTS 而不是 DISTINCT 子查询
* **Item**:SUB.003 * **Item**:SUB.003
* **Severity**:L3 * **Severity**:L3
* **Content**:DISTINCT关键字在对元组排序后删除重复。相反,考虑使用一个带有EXISTS关键字的子查询,您可以避免返回整个表。 * **Content**:DISTINCT 关键字在对元组排序后删除重复。相反,考虑使用一个带有 EXISTS 关键字的子查询,您可以避免返回整个表。
* **Case**: * **Case**:
```sql ```sql
...@@ -1066,7 +1066,7 @@ SELECT * from tb where id in (select id from (select id from tb)) ...@@ -1066,7 +1066,7 @@ SELECT * from tb where id in (select id from (select id from tb))
* **Item**:SUB.005 * **Item**:SUB.005
* **Severity**:L8 * **Severity**:L8
* **Content**:当前MySQL版本不支持在子查询中进行'LIMIT & IN/ALL/ANY/SOME'。 * **Content**:当前 MySQL 版本不支持在子查询中进行 'LIMIT & IN/ALL/ANY/SOME'。
* **Case**: * **Case**:
```sql ```sql
......
...@@ -116,7 +116,7 @@ SELECT * FROM tb WHERE col IN (NULL); ...@@ -116,7 +116,7 @@ SELECT * FROM tb WHERE col IN (NULL);
* **Item**:ARG.005 * **Item**:ARG.005
* **Severity**:L1 * **Severity**:L1
* **Content**: 如:select id from t where num in(1,2,3)对于连续的数值,能用BETWEEN就不要用IN了:select id from t where num between 1 and 3。而当IN值过多时MySQL也可能会进入全表扫描导致性能急剧下降。 * **Content**: 如:select id from t where num in(1,2,3)对于连续的数值,能用 BETWEEN 就不要用 IN 了:select id from t where num between 1 and 3。而当IN值过多时MySQL也可能会进入全表扫描导致性能急剧下降。
* **Case**: * **Case**:
```sql ```sql
...@@ -136,13 +136,13 @@ select id from t where num is null ...@@ -136,13 +136,13 @@ select id from t where num is null
* **Item**:ARG.007 * **Item**:ARG.007
* **Severity**:L3 * **Severity**:L3
* **Content**:性能问题是使用模式匹配操作符的最大缺点。使用LIKE或正则表达式进行模式匹配进行查询的另一个问题,是可能会返回意料之外的结果。最好的方案就是使用特殊的搜索引擎技术来替代SQL,比如Apache Lucene。另一个可选方案是将结果保存起来从而减少重复的搜索开销。如果一定要使用SQL,请考虑在MySQL中使用像FULLTEXT索引这样的第三方扩展。但更广泛地说,您不一定要使用SQL来解决所有问题。 * **Content**:性能问题是使用模式匹配操作符的最大缺点。使用LIKE或正则表达式进行模式匹配进行查询的另一个问题,是可能会返回意料之外的结果。最好的方案就是使用特殊的搜索引擎技术来替代SQL,比如 Apache Lucene。另一个可选方案是将结果保存起来从而减少重复的搜索开销。如果一定要使用SQL,请考虑在 MySQL 中使用像 FULLTEXT 索引这样的第三方扩展。但更广泛地说,您不一定要使用SQL来解决所有问题。
* **Case**: * **Case**:
```sql ```sql
select c_id,c2,c3 from tbl where c2 like 'test%' select c_id,c2,c3 from tbl where c2 like 'test%'
``` ```
## OR查询索引列时请尽量使用IN谓词 ## OR 查询索引列时请尽量使用 IN 谓词
* **Item**:ARG.008 * **Item**:ARG.008
* **Severity**:L1 * **Severity**:L1
...@@ -156,7 +156,7 @@ SELECT c1,c2,c3 FROM tbl WHERE c1 = 14 OR c1 = 17 ...@@ -156,7 +156,7 @@ SELECT c1,c2,c3 FROM tbl WHERE c1 = 14 OR c1 = 17
* **Item**:ARG.009 * **Item**:ARG.009
* **Severity**:L1 * **Severity**:L1
* **Content**:如果VARCHAR列的前后存在空格将可能引起逻辑问题,如在MySQL 5.5中'a'和'a '可能会在查询中被认为是相同的值。 * **Content**:如果VARCHAR列的前后存在空格将可能引起逻辑问题,如在MySQL 5.5中 'a' 和 'a ' 可能会在查询中被认为是相同的值。
* **Case**: * **Case**:
```sql ```sql
...@@ -166,7 +166,7 @@ SELECT 'abc ' ...@@ -166,7 +166,7 @@ SELECT 'abc '
* **Item**:ARG.010 * **Item**:ARG.010
* **Severity**:L1 * **Severity**:L1
* **Content**:hint是用来强制SQL按照某个执行计划来执行,但随着数据量变化我们无法保证自己当初的预判是正确的。 * **Content**:hint 是用来强制 SQL 按照某个执行计划来执行,但随着数据量变化我们无法保证自己当初的预判是正确的。
* **Case**: * **Case**:
```sql ```sql
...@@ -536,23 +536,23 @@ SELECT COUNT(DISTINCT col, col2) FROM tbl; ...@@ -536,23 +536,23 @@ SELECT COUNT(DISTINCT col, col2) FROM tbl;
* **Item**:DIS.003 * **Item**:DIS.003
* **Severity**:L3 * **Severity**:L3
* **Content**:当表已经有主键时,对所有列进行DISTINCT的输出结果与不进行DISTINCT操作的结果相同,请不要画蛇添足。 * **Content**:当表已经有主键时,对所有列进行 DISTINCT 的输出结果与不进行 DISTINCT 操作的结果相同,请不要画蛇添足。
* **Case**: * **Case**:
```sql ```sql
SELECT DISTINCT * FROM film; SELECT DISTINCT * FROM film;
``` ```
## 避免在WHERE条件中使用函数或其他运算符 ## 避免在 WHERE 条件中使用函数或其他运算符
* **Item**:FUN.001 * **Item**:FUN.001
* **Severity**:L2 * **Severity**:L2
* **Content**:虽然在SQL中使用函数可以简化很多复杂的查询,但使用了函数的查询无法利用表中已经建立的索引,该查询将会是全表扫描,性能较差。通常建议将列名写在比较运算符左侧,将查询过滤条件放在比较运算符右侧 * **Content**:虽然在 SQL 中使用函数可以简化很多复杂的查询,但使用了函数的查询无法利用表中已经建立的索引,该查询将会是全表扫描,性能较差。通常建议将列名写在比较运算符左侧,将查询过滤条件放在比较运算符右侧。也不建议在查询比较条件两侧书写多余的括号,这会对阅读产生比较大的困扰
* **Case**: * **Case**:
```sql ```sql
select id from t where substring(name,1,3)='abc' select id from t where substring(name,1,3)='abc'
``` ```
## 指定了WHERE条件或非MyISAM引擎时使用COUNT(\*)操作性能不佳 ## 指定了 WHERE 条件或非 MyISAM 引擎时使用 COUNT(\*) 操作性能不佳
* **Item**:FUN.002 * **Item**:FUN.002
* **Severity**:L1 * **Severity**:L1
...@@ -606,13 +606,13 @@ SELECT SUM(COL) FROM tbl; ...@@ -606,13 +606,13 @@ SELECT SUM(COL) FROM tbl;
* **Item**:GRP.001 * **Item**:GRP.001
* **Severity**:L2 * **Severity**:L2
* **Content**:GROUP BY中的列在前面的WHERE条件中使用了等值查询,对这样的列进行GROUP BY意义不大。 * **Content**:GROUP BY 中的列在前面的 WHERE 条件中使用了等值查询,对这样的列进行GROUP BY意义不大。
* **Case**: * **Case**:
```sql ```sql
select film_id, title from film where release_year='2006' group by release_year select film_id, title from film where release_year='2006' group by release_year
``` ```
## JOIN语句混用逗号和ANSI模式 ## JOIN 语句混用逗号和 ANSI 模式
* **Item**:JOI.001 * **Item**:JOI.001
* **Severity**:L2 * **Severity**:L2
...@@ -636,7 +636,7 @@ select tb1.col from (tb1, tb2) join tb2 on tb1.id=tb.id where tb1.id=1 ...@@ -636,7 +636,7 @@ select tb1.col from (tb1, tb2) join tb2 on tb1.id=tb.id where tb1.id=1
* **Item**:JOI.003 * **Item**:JOI.003
* **Severity**:L4 * **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**: * **Case**:
```sql ```sql
...@@ -646,7 +646,7 @@ select c1,c2,c3 from t1 left outer join t2 using(c1) where t1.c2=2 and t2.c3=4 ...@@ -646,7 +646,7 @@ select c1,c2,c3 from t1 left outer join t2 using(c1) where t1.c2=2 and t2.c3=4
* **Item**:JOI.004 * **Item**:JOI.004
* **Severity**:L4 * **Severity**:L4
* **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。 * **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**: * **Case**:
```sql ```sql
...@@ -666,7 +666,7 @@ select bp1.p_id, b1.d_d as l, b1.b_id from b1 join bp1 on (b1.b_id = bp1.b_id) l ...@@ -666,7 +666,7 @@ select bp1.p_id, b1.d_d as l, b1.b_id from b1 join bp1 on (b1.b_id = bp1.b_id) l
* **Item**:JOI.006 * **Item**:JOI.006
* **Severity**:L4 * **Severity**:L4
* **Content**:一般来说,非嵌套子查询总是用于关联子查询,最多是来自FROM子句中的一个表,这些子查询用于ANY、ALL和EXISTS的谓词。如果可以根据查询语义决定子查询最多返回一个行,那么一个不相关的子查询或来自FROM子句中的多个表的子查询就被压平了。 * **Content**:一般来说,非嵌套子查询总是用于关联子查询,最多是来自FROM子句中的一个表,这些子查询用于 ANY, ALL 和 EXISTS 的谓词。如果可以根据查询语义决定子查询最多返回一个行,那么一个不相关的子查询或来自FROM子句中的多个表的子查询就被压平了。
* **Case**: * **Case**:
```sql ```sql
...@@ -682,11 +682,11 @@ SELECT s,p,d FROM tbl WHERE p.p_id = (SELECT s.p_id FROM tbl WHERE s.c_id = 1009 ...@@ -682,11 +682,11 @@ SELECT s,p,d FROM tbl WHERE p.p_id = (SELECT s.p_id FROM tbl WHERE s.c_id = 1009
```sql ```sql
UPDATE users u LEFT JOIN hobby h ON u.id = h.uid SET u.name = 'pianoboy' WHERE h.hobby = 'piano'; UPDATE users u LEFT JOIN hobby h ON u.id = h.uid SET u.name = 'pianoboy' WHERE h.hobby = 'piano';
``` ```
## 不要使用跨DB的Join查询 ## 不要使用跨数据库的 JOIN 查询
* **Item**:JOI.008 * **Item**:JOI.008
* **Severity**:L4 * **Severity**:L4
* **Content**:一般来说,跨DB的Join查询意味着查询语句跨越了两个不同的子系统,这可能意味着系统耦合度过高或库表结构设计不合理。 * **Content**:一般来说,跨数据库的 JOIN 查询意味着查询语句跨越了两个不同的子系统,这可能意味着系统耦合度过高或库表结构设计不合理。
* **Case**: * **Case**:
```sql ```sql
...@@ -752,11 +752,11 @@ CREATE TABLE tbl ( a int, b int, c int, KEY idx_a (`a`),KEY idx_b(`b`),KEY idx_c ...@@ -752,11 +752,11 @@ CREATE TABLE tbl ( a int, b int, c int, KEY idx_a (`a`),KEY idx_b(`b`),KEY idx_c
```sql ```sql
CREATE TABLE tbl ( a int, b int, c int, PRIMARY KEY(`a`,`b`,`c`)); CREATE TABLE tbl ( a int, b int, c int, PRIMARY KEY(`a`,`b`,`c`));
``` ```
## 未指定主键或主键非int或bigint ## 未指定主键或主键非 int 或 bigint
* **Item**:KEY.007 * **Item**:KEY.007
* **Severity**:L4 * **Severity**:L4
* **Content**:未指定主键或主键非int或bigint,建议将主键设置为int unsigned或bigint unsigned。 * **Content**:未指定主键或主键非 int 或 bigint,建议将主键设置为 int unsigned 或 bigint unsigned。
* **Case**: * **Case**:
```sql ```sql
...@@ -782,7 +782,7 @@ SELECT * FROM tbl ORDER BY a DESC, b ASC; ...@@ -782,7 +782,7 @@ SELECT * FROM tbl ORDER BY a DESC, b ASC;
```sql ```sql
CREATE UNIQUE INDEX part_of_name ON customer (name(10)); CREATE UNIQUE INDEX part_of_name ON customer (name(10));
``` ```
## SQL\_CALC\_FOUND\_ROWS效率低下 ## SQL\_CALC\_FOUND\_ROWS 效率低下
* **Item**:KWR.001 * **Item**:KWR.001
* **Severity**:L2 * **Severity**:L2
...@@ -866,7 +866,7 @@ select c1,c2,c3,c4 from tab1 where col_id REGEXP '[[:<:]]12[[:>:]]' ...@@ -866,7 +866,7 @@ select c1,c2,c3,c4 from tab1 where col_id REGEXP '[[:<:]]12[[:>:]]'
* **Item**:LIT.004 * **Item**:LIT.004
* **Severity**:L1 * **Severity**:L1
* **Content**:USE database, SHOW DATABASES等命令也需要使用使用分号或已设定的DELIMITER结尾。 * **Content**:USE database, SHOW DATABASES 等命令也需要使用使用分号或已设定的 DELIMITER 结尾。
* **Case**: * **Case**:
```sql ```sql
...@@ -882,11 +882,11 @@ USE db ...@@ -882,11 +882,11 @@ USE db
```sql ```sql
select c1,c2,c3 from t1 where c2='foo' group by c2 select c1,c2,c3 from t1 where c2='foo' group by c2
``` ```
## 未使用ORDER BY的LIMIT查询 ## 未使用 ORDER BY 的 LIMIT 查询
* **Item**:RES.002 * **Item**:RES.002
* **Severity**:L4 * **Severity**:L4
* **Content**:没有ORDER BY的LIMIT会导致非确定性的结果,这取决于查询执行计划。 * **Content**:没有 ORDER BY 的 LIMIT 会导致非确定性的结果,这取决于查询执行计划。
* **Case**: * **Case**:
```sql ```sql
...@@ -936,7 +936,7 @@ select * from tbl where 1 != 1; ...@@ -936,7 +936,7 @@ select * from tbl where 1 != 1;
* **Item**:RES.007 * **Item**:RES.007
* **Severity**:L4 * **Severity**:L4
* **Content**:查询条件永远为真,这将导致WHERE条件失效进行全表查询。 * **Content**:查询条件永远为真,可能导致 WHERE 条件失效进行全表查询。
* **Case**: * **Case**:
```sql ```sql
...@@ -946,7 +946,7 @@ select * from tbl where 1 = 1; ...@@ -946,7 +946,7 @@ select * from tbl where 1 = 1;
* **Item**:RES.008 * **Item**:RES.008
* **Severity**:L2 * **Severity**:L2
* **Content**:SELECT INTO OUTFILE需要授予FILE权限,这通过会引入安全问题。LOAD DATA虽然可以提高数据导入速度,但同时也可能导致从库同步延迟过大。 * **Content**:SELECT INTO OUTFILE 需要授予 FILE 权限,这通过会引入安全问题。LOAD DATA 虽然可以提高数据导入速度,但同时也可能导致从库同步延迟过大。
* **Case**: * **Case**:
```sql ```sql
...@@ -996,7 +996,7 @@ select col1,col2 from tbl where type!=0 ...@@ -996,7 +996,7 @@ select col1,col2 from tbl where type!=0
* **Item**:STA.002 * **Item**:STA.002
* **Severity**:L1 * **Severity**:L1
* **Content**:当使用db.table或table.column格式访问表或字段时,请不要在点号后面添加空格,虽然这样语法正确。 * **Content**:当使用 db.table 或 table.column 格式访问表或字段时,请不要在点号后面添加空格,虽然这样语法正确。
* **Case**: * **Case**:
```sql ```sql
...@@ -1032,7 +1032,7 @@ CREATE TABLE ` abc` (a int); ...@@ -1032,7 +1032,7 @@ CREATE TABLE ` abc` (a int);
```sql ```sql
select col1,col2,col3 from table1 where col2 in(select col from table2) select col1,col2,col3 from table1 where col2 in(select col from table2)
``` ```
## 如果您不在乎重复的话,建议使用UNION ALL替代UNION ## 如果您不在乎重复的话,建议使用 UNION ALL 替代 UNION
* **Item**:SUB.002 * **Item**:SUB.002
* **Severity**:L2 * **Severity**:L2
...@@ -1042,11 +1042,11 @@ select col1,col2,col3 from table1 where col2 in(select col from table2) ...@@ -1042,11 +1042,11 @@ select col1,col2,col3 from table1 where col2 in(select col from table2)
```sql ```sql
select teacher_id as id,people_name as name from t1,t2 where t1.teacher_id=t2.people_id union select student_id as id,people_name as name from t1,t2 where t1.student_id=t2.people_id select teacher_id as id,people_name as name from t1,t2 where t1.teacher_id=t2.people_id union select student_id as id,people_name as name from t1,t2 where t1.student_id=t2.people_id
``` ```
## 考虑使用EXISTS而不是DISTINCT子查询 ## 考虑使用 EXISTS 而不是 DISTINCT 子查询
* **Item**:SUB.003 * **Item**:SUB.003
* **Severity**:L3 * **Severity**:L3
* **Content**:DISTINCT关键字在对元组排序后删除重复。相反,考虑使用一个带有EXISTS关键字的子查询,您可以避免返回整个表。 * **Content**:DISTINCT 关键字在对元组排序后删除重复。相反,考虑使用一个带有 EXISTS 关键字的子查询,您可以避免返回整个表。
* **Case**: * **Case**:
```sql ```sql
...@@ -1066,7 +1066,7 @@ SELECT * from tb where id in (select id from (select id from tb)) ...@@ -1066,7 +1066,7 @@ SELECT * from tb where id in (select id from (select id from tb))
* **Item**:SUB.005 * **Item**:SUB.005
* **Severity**:L8 * **Severity**:L8
* **Content**:当前MySQL版本不支持在子查询中进行'LIMIT & IN/ALL/ANY/SOME'。 * **Content**:当前 MySQL 版本不支持在子查询中进行 'LIMIT & IN/ALL/ANY/SOME'。
* **Case**: * **Case**:
```sql ```sql
......
...@@ -1016,68 +1016,68 @@ ...@@ -1016,68 +1016,68 @@
{ {
"checksumSHA1": "w8FCRjH70gM6QttB9QrEh9Y1x64=", "checksumSHA1": "w8FCRjH70gM6QttB9QrEh9Y1x64=",
"path": "vitess.io/vitess", "path": "vitess.io/vitess",
"revision": "5b3aedd79bd1488060ae5c0551b31685892c6eed", "revision": "088b121f3ef2d0f0631499fcdf77eed9b701b5fe",
"revisionTime": "2018-11-14T21:08:06Z" "revisionTime": "2018-11-16T06:46:05Z"
}, },
{ {
"checksumSHA1": "aKn1oKcY74N8TRLm3Ayt7Q4bbI4=", "checksumSHA1": "aKn1oKcY74N8TRLm3Ayt7Q4bbI4=",
"path": "vitess.io/vitess/go/bytes2", "path": "vitess.io/vitess/go/bytes2",
"revision": "5b3aedd79bd1488060ae5c0551b31685892c6eed", "revision": "088b121f3ef2d0f0631499fcdf77eed9b701b5fe",
"revisionTime": "2018-11-14T21:08:06Z" "revisionTime": "2018-11-16T06:46:05Z"
}, },
{ {
"checksumSHA1": "JVCEN4UGRmg3TofIBdzZMZ3G0Ww=", "checksumSHA1": "JVCEN4UGRmg3TofIBdzZMZ3G0Ww=",
"path": "vitess.io/vitess/go/hack", "path": "vitess.io/vitess/go/hack",
"revision": "5b3aedd79bd1488060ae5c0551b31685892c6eed", "revision": "088b121f3ef2d0f0631499fcdf77eed9b701b5fe",
"revisionTime": "2018-11-14T21:08:06Z" "revisionTime": "2018-11-16T06:46:05Z"
}, },
{ {
"checksumSHA1": "e1WJ7vCnVrlQQQlc6n/FewCDMso=", "checksumSHA1": "e1WJ7vCnVrlQQQlc6n/FewCDMso=",
"path": "vitess.io/vitess/go/sqltypes", "path": "vitess.io/vitess/go/sqltypes",
"revision": "5b3aedd79bd1488060ae5c0551b31685892c6eed", "revision": "088b121f3ef2d0f0631499fcdf77eed9b701b5fe",
"revisionTime": "2018-11-14T21:08:06Z" "revisionTime": "2018-11-16T06:46:05Z"
}, },
{ {
"checksumSHA1": "ntFIQYkBS51G6y+FEkjFW40+HOU=", "checksumSHA1": "ntFIQYkBS51G6y+FEkjFW40+HOU=",
"path": "vitess.io/vitess/go/vt/log", "path": "vitess.io/vitess/go/vt/log",
"revision": "5b3aedd79bd1488060ae5c0551b31685892c6eed", "revision": "088b121f3ef2d0f0631499fcdf77eed9b701b5fe",
"revisionTime": "2018-11-14T21:08:06Z" "revisionTime": "2018-11-16T06:46:05Z"
}, },
{ {
"checksumSHA1": "XozR8bmeSR5KTe/nlUJkpJY2HKI=", "checksumSHA1": "XozR8bmeSR5KTe/nlUJkpJY2HKI=",
"path": "vitess.io/vitess/go/vt/proto/query", "path": "vitess.io/vitess/go/vt/proto/query",
"revision": "5b3aedd79bd1488060ae5c0551b31685892c6eed", "revision": "088b121f3ef2d0f0631499fcdf77eed9b701b5fe",
"revisionTime": "2018-11-14T21:08:06Z" "revisionTime": "2018-11-16T06:46:05Z"
}, },
{ {
"checksumSHA1": "OnWsUHLDKcO3spwH0jD55SvKD24=", "checksumSHA1": "OnWsUHLDKcO3spwH0jD55SvKD24=",
"path": "vitess.io/vitess/go/vt/proto/topodata", "path": "vitess.io/vitess/go/vt/proto/topodata",
"revision": "5b3aedd79bd1488060ae5c0551b31685892c6eed", "revision": "088b121f3ef2d0f0631499fcdf77eed9b701b5fe",
"revisionTime": "2018-11-14T21:08:06Z" "revisionTime": "2018-11-16T06:46:05Z"
}, },
{ {
"checksumSHA1": "sBAuZ/itMR8U8qbK4yLHxkP6Cpc=", "checksumSHA1": "sBAuZ/itMR8U8qbK4yLHxkP6Cpc=",
"path": "vitess.io/vitess/go/vt/proto/vtgate", "path": "vitess.io/vitess/go/vt/proto/vtgate",
"revision": "5b3aedd79bd1488060ae5c0551b31685892c6eed", "revision": "088b121f3ef2d0f0631499fcdf77eed9b701b5fe",
"revisionTime": "2018-11-14T21:08:06Z" "revisionTime": "2018-11-16T06:46:05Z"
}, },
{ {
"checksumSHA1": "pLWM+SPGZs3k+IhjktE/cGUlpM0=", "checksumSHA1": "pLWM+SPGZs3k+IhjktE/cGUlpM0=",
"path": "vitess.io/vitess/go/vt/proto/vtrpc", "path": "vitess.io/vitess/go/vt/proto/vtrpc",
"revision": "5b3aedd79bd1488060ae5c0551b31685892c6eed", "revision": "088b121f3ef2d0f0631499fcdf77eed9b701b5fe",
"revisionTime": "2018-11-14T21:08:06Z" "revisionTime": "2018-11-16T06:46:05Z"
}, },
{ {
"checksumSHA1": "3ggEFYVEhMPxyhkKhRGw3x1eZ9M=", "checksumSHA1": "3ggEFYVEhMPxyhkKhRGw3x1eZ9M=",
"path": "vitess.io/vitess/go/vt/sqlparser", "path": "vitess.io/vitess/go/vt/sqlparser",
"revision": "5b3aedd79bd1488060ae5c0551b31685892c6eed", "revision": "088b121f3ef2d0f0631499fcdf77eed9b701b5fe",
"revisionTime": "2018-11-14T21:08:06Z" "revisionTime": "2018-11-16T06:46:05Z"
}, },
{ {
"checksumSHA1": "oF4XzuOzwvj1iduX/lYqNSyY/HM=", "checksumSHA1": "oF4XzuOzwvj1iduX/lYqNSyY/HM=",
"path": "vitess.io/vitess/go/vt/vterrors", "path": "vitess.io/vitess/go/vt/vterrors",
"revision": "5b3aedd79bd1488060ae5c0551b31685892c6eed", "revision": "088b121f3ef2d0f0631499fcdf77eed9b701b5fe",
"revisionTime": "2018-11-14T21:08:06Z" "revisionTime": "2018-11-16T06:46:05Z"
} }
], ],
"rootPath": "github.com/XiaoMi/soar" "rootPath": "github.com/XiaoMi/soar"
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册