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

update rules doc lint

上级 b11fa0cd
...@@ -79,7 +79,7 @@ build: fmt tidb-parser ...@@ -79,7 +79,7 @@ build: fmt tidb-parser
@echo "build Success!" @echo "build Success!"
.PHONY: fast .PHONY: fast
fast: fast: fmt
@echo "\033[92mBuilding ...\033[0m" @echo "\033[92mBuilding ...\033[0m"
@bash ./genver.sh $(GO_VERSION_MIN) @bash ./genver.sh $(GO_VERSION_MIN)
@ret=0 && for d in $$(go list -f '{{if (eq .Name "main")}}{{.ImportPath}}{{end}}' ./...); do \ @ret=0 && for d in $$(go list -f '{{if (eq .Name "main")}}{{.ImportPath}}{{end}}' ./...); do \
......
...@@ -530,7 +530,6 @@ func (q *Query4Audit) RuleDiffGroupByOrderBy() Rule { ...@@ -530,7 +530,6 @@ func (q *Query4Audit) RuleDiffGroupByOrderBy() Rule {
groupbyTbls = append(groupbyTbls, g.Qualifier.Name) groupbyTbls = append(groupbyTbls, g.Qualifier.Name)
if len(groupbyTbls) > 1 { if len(groupbyTbls) > 1 {
rule = HeuristicRules["CLA.006"] rule = HeuristicRules["CLA.006"]
return false, nil return false, nil
} }
} }
...@@ -551,7 +550,6 @@ func (q *Query4Audit) RuleDiffGroupByOrderBy() Rule { ...@@ -551,7 +550,6 @@ func (q *Query4Audit) RuleDiffGroupByOrderBy() Rule {
orderbyTbls = append(orderbyTbls, o.Qualifier.Name) orderbyTbls = append(orderbyTbls, o.Qualifier.Name)
if len(orderbyTbls) > 1 { if len(orderbyTbls) > 1 {
rule = HeuristicRules["CLA.006"] rule = HeuristicRules["CLA.006"]
return false, nil return false, nil
} }
} }
...@@ -573,7 +571,6 @@ func (q *Query4Audit) RuleDiffGroupByOrderBy() Rule { ...@@ -573,7 +571,6 @@ func (q *Query4Audit) RuleDiffGroupByOrderBy() Rule {
} }
if !tblExist && len(orderbyTbls) > 0 { if !tblExist && len(orderbyTbls) > 0 {
rule = HeuristicRules["CLA.006"] rule = HeuristicRules["CLA.006"]
return rule return rule
} }
} }
...@@ -593,7 +590,6 @@ func (q *Query4Audit) RuleMixOrderBy() Rule { ...@@ -593,7 +590,6 @@ func (q *Query4Audit) RuleMixOrderBy() Rule {
// 比较相邻两个order by列的方向 // 比较相邻两个order by列的方向
if direction != "" && order.Direction != direction { if direction != "" && order.Direction != direction {
rule = HeuristicRules["CLA.007"] rule = HeuristicRules["CLA.007"]
return false, nil return false, nil
} }
direction = order.Direction direction = order.Direction
...@@ -614,7 +610,6 @@ func (q *Query4Audit) RuleExplicitOrderBy() Rule { ...@@ -614,7 +610,6 @@ func (q *Query4Audit) RuleExplicitOrderBy() Rule {
// 有group by,但没有order by // 有group by,但没有order by
if n.GroupBy != nil && n.OrderBy == nil { if n.GroupBy != nil && n.OrderBy == nil {
rule = HeuristicRules["CLA.008"] rule = HeuristicRules["CLA.008"]
return false, nil return false, nil
} }
} }
...@@ -638,7 +633,6 @@ func (q *Query4Audit) RuleOrderByExpr() Rule { ...@@ -638,7 +633,6 @@ func (q *Query4Audit) RuleOrderByExpr() Rule {
// 函数名方式,如:from_unixtime(col) // 函数名方式,如:from_unixtime(col)
if funcExp.MatchString(orderBy) { if funcExp.MatchString(orderBy) {
rule = HeuristicRules["CLA.009"] rule = HeuristicRules["CLA.009"]
return false, nil return false, nil
} }
...@@ -648,7 +642,6 @@ func (q *Query4Audit) RuleOrderByExpr() Rule { ...@@ -648,7 +642,6 @@ func (q *Query4Audit) RuleOrderByExpr() Rule {
}) })
if string(trim) != "" { if string(trim) != "" {
rule = HeuristicRules["CLA.009"] rule = HeuristicRules["CLA.009"]
return false, nil return false, nil
} }
...@@ -678,12 +671,10 @@ func (q *Query4Audit) RuleOrderByExpr() Rule { ...@@ -678,12 +671,10 @@ func (q *Query4Audit) RuleOrderByExpr() Rule {
}) })
if string(trim) != "" { if string(trim) != "" {
rule = HeuristicRules["CLA.009"] rule = HeuristicRules["CLA.009"]
} }
// 函数 // 函数
if funcExp.MatchString(s) { if funcExp.MatchString(s) {
rule = HeuristicRules["CLA.009"] rule = HeuristicRules["CLA.009"]
} }
} }
} }
...@@ -705,7 +696,6 @@ func (q *Query4Audit) RuleGroupByExpr() Rule { ...@@ -705,7 +696,6 @@ func (q *Query4Audit) RuleGroupByExpr() Rule {
// 函数名方式,如:from_unixtime(col) // 函数名方式,如:from_unixtime(col)
if funcExp.MatchString(groupBy) { if funcExp.MatchString(groupBy) {
rule = HeuristicRules["CLA.010"] rule = HeuristicRules["CLA.010"]
return false, nil return false, nil
} }
...@@ -715,7 +705,6 @@ func (q *Query4Audit) RuleGroupByExpr() Rule { ...@@ -715,7 +705,6 @@ func (q *Query4Audit) RuleGroupByExpr() Rule {
}) })
if string(trim) != "" { if string(trim) != "" {
rule = HeuristicRules["CLA.010"] rule = HeuristicRules["CLA.010"]
return false, nil return false, nil
} }
...@@ -745,12 +734,10 @@ func (q *Query4Audit) RuleGroupByExpr() Rule { ...@@ -745,12 +734,10 @@ func (q *Query4Audit) RuleGroupByExpr() Rule {
}) })
if string(trim) != "" { if string(trim) != "" {
rule = HeuristicRules["CLA.010"] rule = HeuristicRules["CLA.010"]
} }
// 函数 // 函数
if funcExp.MatchString(s) { if funcExp.MatchString(s) {
rule = HeuristicRules["CLA.010"] rule = HeuristicRules["CLA.010"]
} }
} }
} }
......
...@@ -321,8 +321,8 @@ func TestRuleOrderByExpr(t *testing.T) { ...@@ -321,8 +321,8 @@ func TestRuleOrderByExpr(t *testing.T) {
common.Log.Debug("Entering function: %s", common.GetFunctionName()) common.Log.Debug("Entering function: %s", common.GetFunctionName())
sqls := [][]string{ sqls := [][]string{
{ {
"SELECT col FROM tbl order by cola - colb;", // order by 列运算 "SELECT col FROM tbl order by cola - cl;", // order by 列运算
"SELECT cola - colb col FROM tbl order by col;", // 别名为列运算 "SELECT cola - cl col FROM tbl order by col;", // 别名为列运算
"SELECT cola FROM tbl order by from_unixtime(col);", // order by 函数运算 "SELECT cola FROM tbl order by from_unixtime(col);", // order by 函数运算
"SELECT from_unixtime(col) cola FROM tbl order by cola;", // 别名为函数运算 "SELECT from_unixtime(col) cola FROM tbl order by cola;", // 别名为函数运算
}, },
...@@ -363,8 +363,8 @@ func TestRuleOrderByExpr(t *testing.T) { ...@@ -363,8 +363,8 @@ func TestRuleOrderByExpr(t *testing.T) {
func TestRuleGroupByExpr(t *testing.T) { func TestRuleGroupByExpr(t *testing.T) {
common.Log.Debug("Entering function: %s", common.GetFunctionName()) common.Log.Debug("Entering function: %s", common.GetFunctionName())
sqls := []string{ sqls := []string{
"SELECT col FROM tbl GROUP by cola - colb;", "SELECT col FROM tbl GROUP by cola - col;",
"SELECT cola - colb col FROM tbl GROUP by col;", "SELECT cola - col col FROM tbl GROUP by col;",
"SELECT cola FROM tbl GROUP by from_unixtime(col);", "SELECT cola FROM tbl GROUP by from_unixtime(col);",
"SELECT from_unixtime(col) cola FROM tbl GROUP by cola;", "SELECT from_unixtime(col) cola FROM tbl GROUP by cola;",
...@@ -397,7 +397,7 @@ func TestRuleTblCommentCheck(t *testing.T) { ...@@ -397,7 +397,7 @@ func TestRuleTblCommentCheck(t *testing.T) {
" `c3` varchar(32) DEFAULT NULL, `c4` int(11) NOT NULL, `c5` double NOT NULL," + " `c3` varchar(32) DEFAULT NULL, `c4` int(11) NOT NULL, `c5` double NOT NULL," +
" `c6` text NOT NULL, PRIMARY KEY (`ID`), KEY `idx_c3_c2_c4_c5_c6` " + " `c6` text NOT NULL, PRIMARY KEY (`ID`), KEY `idx_c3_c2_c4_c5_c6` " +
"(`c3`,`c2`(255),`c4`,`c5`,`c6`(255)), KEY `idx_c3_c2_c4` (`c3`,`c2`,`c4`)) " + "(`c3`,`c2`(255),`c4`,`c5`,`c6`(255)), KEY `idx_c3_c2_c4` (`c3`,`c2`,`c4`)) " +
"ENGINE=InnoDB DEFAULT CHARSET=utf8", "ENGINE = InnoDB DEFAULT CHARSET=utf8",
} }
for _, sql := range sqls { for _, sql := range sqls {
q, err := NewQuery4Audit(sql) q, err := NewQuery4Audit(sql)
...@@ -1484,7 +1484,7 @@ func TestRuleReduceNumberOfJoin(t *testing.T) { ...@@ -1484,7 +1484,7 @@ func TestRuleReduceNumberOfJoin(t *testing.T) {
func TestRuleDistinctUsage(t *testing.T) { func TestRuleDistinctUsage(t *testing.T) {
common.Log.Debug("Entering function: %s", common.GetFunctionName()) common.Log.Debug("Entering function: %s", common.GetFunctionName())
sqls := []string{ sqls := []string{
`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 xing, name from B) as e WHERE e.country_id = c.country_id;`, `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;`,
} }
for _, sql := range sqls { for _, sql := range sqls {
q, err := NewQuery4Audit(sql) q, err := NewQuery4Audit(sql)
...@@ -1604,7 +1604,7 @@ func TestRuleForbiddenSyntax(t *testing.T) { ...@@ -1604,7 +1604,7 @@ func TestRuleForbiddenSyntax(t *testing.T) {
common.Log.Debug("Entering function: %s", common.GetFunctionName()) common.Log.Debug("Entering function: %s", common.GetFunctionName())
sqls := []string{ sqls := []string{
`create view v_today (today) AS SELECT CURRENT_DATE;`, `create view v_today (today) AS SELECT CURRENT_DATE;`,
`CREATE VIEW v (mycol) AS SELECT 'abc';`, `CREATE VIEW v (col) AS SELECT 'abc';`,
`CREATE FUNCTION hello (s CHAR(20));`, `CREATE FUNCTION hello (s CHAR(20));`,
`CREATE PROCEDURE simpleproc (OUT param1 INT)`, `CREATE PROCEDURE simpleproc (OUT param1 INT)`,
} }
...@@ -2292,8 +2292,8 @@ func TestRuleCantBeNull(t *testing.T) { ...@@ -2292,8 +2292,8 @@ func TestRuleCantBeNull(t *testing.T) {
func TestRuleTooManyKeyParts(t *testing.T) { func TestRuleTooManyKeyParts(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 NOT NULL DEFAULT '', PRIMARY KEY (`id`));", "CREATE TABLE `tb` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `c` longblob NOT NULL DEFAULT '', PRIMARY KEY (`id`));",
"alter TABLE `sbtest` add index idx_idx (`id`);", "alter TABLE `tb` add index idx_idx (`id`);",
} }
for _, sql := range sqls { for _, sql := range sqls {
q, err := NewQuery4Audit(sql) q, err := NewQuery4Audit(sql)
...@@ -2498,7 +2498,7 @@ func TestRuleAutoIncrementInitNotZero(t *testing.T) { ...@@ -2498,7 +2498,7 @@ func TestRuleAutoIncrementInitNotZero(t *testing.T) {
sqls := [][]string{ sqls := [][]string{
// 正面的例子 // 正面的例子
{ {
"CREATE TABLE `sbtest` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `pad` char(60) NOT NULL DEFAULT '', PRIMARY KEY (`id`)) ENGINE=InnoDB AUTO_INCREMENT=13", "CREATE TABLE `tb` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `pad` char(60) NOT NULL DEFAULT '', PRIMARY KEY (`id`)) ENGINE=InnoDB AUTO_INCREMENT=13",
}, },
// 反面的例子 // 反面的例子
{ {
...@@ -2543,7 +2543,7 @@ func TestRuleColumnWithCharset(t *testing.T) { ...@@ -2543,7 +2543,7 @@ func TestRuleColumnWithCharset(t *testing.T) {
}, },
// 反面的例子 // 反面的例子
{ {
"CREATE TABLE `sbtest` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `c` char(120) NOT NULL DEFAULT '', PRIMARY KEY (`id`))", "CREATE TABLE `tb` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `c` char(120) NOT NULL DEFAULT '', PRIMARY KEY (`id`))",
}, },
} }
for _, sql := range sqls[0] { for _, sql := range sqls[0] {
...@@ -2615,12 +2615,12 @@ func TestRuleBlobDefaultValue(t *testing.T) { ...@@ -2615,12 +2615,12 @@ func TestRuleBlobDefaultValue(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` blob NOT NULL DEFAULT '', PRIMARY KEY (`id`));", "CREATE TABLE `tb` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `c` blob NOT NULL DEFAULT '', PRIMARY KEY (`id`));",
"alter table `sbtest` add column `c` blob NOT NULL DEFAULT '';", "alter table `tb` add column `c` blob NOT NULL DEFAULT '';",
}, },
{ {
"CREATE TABLE `sbtest` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `c` blob NOT NULL, PRIMARY KEY (`id`));", "CREATE TABLE `tb` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `c` blob NOT NULL, PRIMARY KEY (`id`));",
"alter table `sbtest` add column `c` blob NOT NULL DEFAULT NULL;", "alter table `tb` add column `c` blob NOT NULL DEFAULT NULL;",
}, },
} }
...@@ -2655,18 +2655,18 @@ func TestRuleIntPrecision(t *testing.T) { ...@@ -2655,18 +2655,18 @@ func TestRuleIntPrecision(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(1) );", "CREATE TABLE `tb` ( `id` int(1) );",
"CREATE TABLE `sbtest` ( `id` bigint(1) );", "CREATE TABLE `tb` ( `id` bigint(1) );",
"alter TABLE `sbtest` add column `id` bigint(1);", "alter TABLE `tb` add column `id` bigint(1);",
"alter TABLE `sbtest` add column `id` int(1);", "alter TABLE `tb` add column `id` int(1);",
}, },
{ {
"CREATE TABLE `sbtest` ( `id` int(10));", "CREATE TABLE `tb` ( `id` int(10));",
"CREATE TABLE `sbtest` ( `id` bigint(20));", "CREATE TABLE `tb` ( `id` bigint(20));",
"alter TABLE `sbtest` add column `id` bigint(20);", "alter TABLE `tb` add column `id` bigint(20);",
"alter TABLE `sbtest` add column `id` int(10);", "alter TABLE `tb` add column `id` int(10);",
"CREATE TABLE `sbtest` ( `id` int);", "CREATE TABLE `tb` ( `id` int);",
"alter TABLE `sbtest` add column `id` bigint;", "alter TABLE `tb` add column `id` bigint;",
}, },
} }
...@@ -2701,14 +2701,14 @@ func TestRuleVarcharLength(t *testing.T) { ...@@ -2701,14 +2701,14 @@ func TestRuleVarcharLength(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` varchar(4000) );", "CREATE TABLE `tb` ( `id` varchar(4000) );",
"CREATE TABLE `sbtest` ( `id` varchar(3500) );", "CREATE TABLE `tb` ( `id` varchar(3500) );",
"alter TABLE `sbtest` add column `id` varchar(3500);", "alter TABLE `tb` add column `id` varchar(3500);",
}, },
{ {
"CREATE TABLE `sbtest` ( `id` varchar(1024));", "CREATE TABLE `tb` ( `id` varchar(1024));",
"CREATE TABLE `sbtest` ( `id` varchar(20));", "CREATE TABLE `tb` ( `id` varchar(20));",
"alter TABLE `sbtest` add column `id` varchar(35);", "alter TABLE `tb` add column `id` varchar(35);",
}, },
} }
...@@ -2804,8 +2804,8 @@ func TestRuleAllowEngine(t *testing.T) { ...@@ -2804,8 +2804,8 @@ func TestRuleAllowEngine(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 tbl (a int) engine=myisam;", "CREATE TABLE tbl (a int) engine=MyISAM;",
"ALTER TABLE tbl engine=myisam;", "ALTER TABLE tbl engine=MyISAM;",
"CREATE TABLE tbl (a int);", "CREATE TABLE tbl (a int);",
}, },
{ {
...@@ -2868,7 +2868,7 @@ func TestRulePartitionNotAllowed(t *testing.T) { ...@@ -2868,7 +2868,7 @@ func TestRulePartitionNotAllowed(t *testing.T) {
func TestRuleAutoIncUnsigned(t *testing.T) { func TestRuleAutoIncUnsigned(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) NOT NULL AUTO_INCREMENT, `c` longblob, PRIMARY KEY (`id`));", "CREATE TABLE `tb` ( `id` int(10) NOT NULL AUTO_INCREMENT, `c` longblob, PRIMARY KEY (`id`));",
"ALTER TABLE `tbl` ADD COLUMN `id` int(10) NOT NULL AUTO_INCREMENT;", "ALTER TABLE `tbl` ADD COLUMN `id` int(10) NOT NULL AUTO_INCREMENT;",
} }
for _, sql := range sqls { for _, sql := range sqls {
......
...@@ -123,8 +123,8 @@ func init() { ...@@ -123,8 +123,8 @@ func init() {
"ALI.001": { "ALI.001": {
Item: "ALI.001", Item: "ALI.001",
Severity: "L0", Severity: "L0",
Summary: "建议使用AS关键字显示声明一个别名", Summary: "建议使用 AS 关键字显示声明一个别名",
Content: `在列或表别名(如"tbl AS alias")中, 明确使用AS关键字比隐含别名(如"tbl alias")更易懂。`, Content: `在列或表别名(如"tbl AS alias")中, 明确使用 AS 关键字比隐含别名(如"tbl alias")更易懂。`,
Case: "select name from tbl t1 where id < 1000", Case: "select name from tbl t1 where id < 1000",
Func: (*Query4Audit).RuleImplicitAlias, Func: (*Query4Audit).RuleImplicitAlias,
}, },
...@@ -132,7 +132,7 @@ func init() { ...@@ -132,7 +132,7 @@ func init() {
Item: "ALI.002", Item: "ALI.002",
Severity: "L8", Severity: "L8",
Summary: "不建议给列通配符'*'设置别名", Summary: "不建议给列通配符'*'设置别名",
Content: `例: "SELECT tbl.* col1, col2"上面这条SQL给列通配符设置了别名,这样的SQL可能存在逻辑错误。您可能意在查询col1, 但是代替它的是重命名的是tbl的最后一列。`, Content: `例: "SELECT tbl.* col1, col2"上面这条 SQL 给列通配符设置了别名,这样的SQL可能存在逻辑错误。您可能意在查询 col1, 但是代替它的是重命名的是 tbl 的最后一列。`,
Case: "select tbl.* as c1,c2,c3 from tbl where id < 1000", Case: "select tbl.* as c1,c2,c3 from tbl where id < 1000",
Func: (*Query4Audit).RuleStarAlias, Func: (*Query4Audit).RuleStarAlias,
}, },
...@@ -148,15 +148,15 @@ func init() { ...@@ -148,15 +148,15 @@ func init() {
Item: "ALT.001", Item: "ALT.001",
Severity: "L4", Severity: "L4",
Summary: "修改表的默认字符集不会改表各个字段的字符集", Summary: "修改表的默认字符集不会改表各个字段的字符集",
Content: `很多初学者会将ALTER TABLE tbl_name [DEFAULT] CHARACTER SET 'UTF8'误认为会修改所有字段的字符集,但实际上它只会影响后续新增的字段不会改表已有字段的字符集。如果想修改整张表所有字段的字符集建议使用ALTER TABLE tbl_name CONVERT TO CHARACTER SET charset_name;`, Content: `很多初学者会将 ALTER TABLE tbl_name [DEFAULT] CHARACTER SET 'UTF8' 误认为会修改所有字段的字符集,但实际上它只会影响后续新增的字段不会改表已有字段的字符集。如果想修改整张表所有字段的字符集建议使用 ALTER TABLE tbl_name CONVERT TO CHARACTER SET charset_name;`,
Case: "ALTER TABLE tbl_name CONVERT TO CHARACTER SET charset_name;", Case: "ALTER TABLE tbl_name CONVERT TO CHARACTER SET charset_name;",
Func: (*Query4Audit).RuleAlterCharset, Func: (*Query4Audit).RuleAlterCharset,
}, },
"ALT.002": { "ALT.002": {
Item: "ALT.002", Item: "ALT.002",
Severity: "L2", Severity: "L2",
Summary: "同一张表的多条ALTER请求建议合为一条", Summary: "同一张表的多条 ALTER 请求建议合为一条",
Content: `每次表结构变更对线上服务都会产生影响,即使是能够通过在线工具进行调整也请尽量通过合并ALTER请求的试减少操作次数。`, Content: `每次表结构变更对线上服务都会产生影响,即使是能够通过在线工具进行调整也请尽量通过合并 ALTER 请求的试减少操作次数。`,
Case: "ALTER TABLE tbl ADD COLUMN col int, ADD INDEX idx_col (`col`);", Case: "ALTER TABLE tbl ADD COLUMN col int, ADD INDEX idx_col (`col`);",
Func: (*Query4Audit).RuleOK, // 该建议在indexAdvisor中给 Func: (*Query4Audit).RuleOK, // 该建议在indexAdvisor中给
}, },
...@@ -171,8 +171,8 @@ func init() { ...@@ -171,8 +171,8 @@ func init() {
"ALT.004": { "ALT.004": {
Item: "ALT.004", Item: "ALT.004",
Severity: "L0", Severity: "L0",
Summary: "删除主键和外键为高危操作,操作前请与DBA确认影响", Summary: "删除主键和外键为高危操作,操作前请与 DBA 确认影响",
Content: `主键和外键为关系型数据库中两种重要约束,删除已有约束会打破已有业务逻辑,操作前请业务开发与DBA确认影响,三思而行。`, Content: `主键和外键为关系型数据库中两种重要约束,删除已有约束会打破已有业务逻辑,操作前请业务开发与 DBA 确认影响,三思而行。`,
Case: "ALTER TABLE tbl DROP PRIMARY KEY;", Case: "ALTER TABLE tbl DROP PRIMARY KEY;",
Func: (*Query4Audit).RuleAlterDropKey, Func: (*Query4Audit).RuleAlterDropKey,
}, },
...@@ -180,15 +180,15 @@ func init() { ...@@ -180,15 +180,15 @@ func init() {
Item: "ARG.001", Item: "ARG.001",
Severity: "L4", Severity: "L4",
Summary: "不建议使用前项通配符查找", Summary: "不建议使用前项通配符查找",
Content: `例如“%foo”,查询参数有一个前项通配符的情况无法使用已有索引。`, Content: `例如 "%foo",查询参数有一个前项通配符的情况无法使用已有索引。`,
Case: "select c1,c2,c3 from tbl where name like '%foo'", Case: "select c1,c2,c3 from tbl where name like '%foo'",
Func: (*Query4Audit).RulePrefixLike, Func: (*Query4Audit).RulePrefixLike,
}, },
"ARG.002": { "ARG.002": {
Item: "ARG.002", Item: "ARG.002",
Severity: "L1", Severity: "L1",
Summary: "没有通配符的LIKE查询", Summary: "没有通配符的 LIKE 查询",
Content: `不包含通配符的LIKE查询可能存在逻辑错误,因为逻辑上它与等值查询相同。`, Content: `不包含通配符的 LIKE 查询可能存在逻辑错误,因为逻辑上它与等值查询相同。`,
Case: "select c1,c2,c3 from tbl where name like 'foo'", Case: "select c1,c2,c3 from tbl where name like 'foo'",
Func: (*Query4Audit).RuleEqualLike, Func: (*Query4Audit).RuleEqualLike,
}, },
...@@ -203,24 +203,24 @@ func init() { ...@@ -203,24 +203,24 @@ func init() {
"ARG.004": { "ARG.004": {
Item: "ARG.004", Item: "ARG.004",
Severity: "L4", Severity: "L4",
Summary: "IN (NULL)/NOT IN (NULL)永远非真", Summary: "IN (NULL)/NOT IN (NULL) 永远非真",
Content: "正确的作法是col IN ('val1', 'val2', 'val3') OR col IS NULL", Content: "正确的作法是 col IN ('val1', 'val2', 'val3') OR col IS NULL",
Case: "SELECT * FROM tb WHERE col IN (NULL);", Case: "SELECT * FROM tb WHERE col IN (NULL);",
Func: (*Query4Audit).RuleIn, Func: (*Query4Audit).RuleIn,
}, },
"ARG.005": { "ARG.005": {
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,
}, },
"ARG.006": { "ARG.006": {
Item: "ARG.006", Item: "ARG.006",
Severity: "L1", Severity: "L1",
Summary: "应尽量避免在WHERE子句中对字段进行NULL值判断", Summary: "应尽量避免在 WHERE 子句中对字段进行 NULL 值判断",
Content: `使用IS NULL或IS NOT NULL将可能导致引擎放弃使用索引而进行全表扫描,如:select id from t where num is null;可以在num上设置默认值0,确保表中num列没有null值,然后这样查询: select id from t where num=0;`, Content: `使用 IS NULL 或 IS NOT NULL 将可能导致引擎放弃使用索引而进行全表扫描,如:select id from t where num is null;可以在num上设置默认值0,确保表中 num 列没有 NULL 值,然后这样查询: select id from t where num=0;`,
Case: "select id from t where num is null", Case: "select id from t where num is null",
Func: (*Query4Audit).RuleIsNullIsNotNull, Func: (*Query4Audit).RuleIsNullIsNotNull,
}, },
...@@ -228,7 +228,7 @@ func init() { ...@@ -228,7 +228,7 @@ 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,
}, },
...@@ -236,7 +236,7 @@ func init() { ...@@ -236,7 +236,7 @@ func init() {
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,14 +244,14 @@ func init() { ...@@ -244,14 +244,14 @@ 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,
}, },
"ARG.010": { "ARG.010": {
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,
...@@ -267,16 +267,16 @@ func init() { ...@@ -267,16 +267,16 @@ func init() {
"CLA.001": { "CLA.001": {
Item: "CLA.001", Item: "CLA.001",
Severity: "L4", Severity: "L4",
Summary: "最外层SELECT未指定WHERE条件", Summary: "最外层 SELECT 未指定 WHERE 条件",
Content: `SELECT语句没有WHERE子句,可能检查比预期更多的行(全表扫描)。对于SELECT COUNT(*)类型的请求如果不要求精度,建议使用SHOW TABLE STATUS或EXPLAIN替代。`, Content: `SELECT 语句没有 WHERE 子句,可能检查比预期更多的行(全表扫描)。对于 SELECT COUNT(*) 类型的请求如果不要求精度,建议使用 SHOW TABLE STATUS 或 EXPLAIN 替代。`,
Case: "select id from tbl", Case: "select id from tbl",
Func: (*Query4Audit).RuleNoWhere, Func: (*Query4Audit).RuleNoWhere,
}, },
"CLA.002": { "CLA.002": {
Item: "CLA.002", Item: "CLA.002",
Severity: "L3", Severity: "L3",
Summary: "不建议使用ORDER BY RAND()", Summary: "不建议使用 ORDER BY RAND()",
Content: `ORDER BY RAND()是从结果集中检索随机行的一种非常低效的方法,因为它会对整个结果进行排序并丢弃其大部分数据。`, Content: `ORDER BY RAND() 是从结果集中检索随机行的一种非常低效的方法,因为它会对整个结果进行排序并丢弃其大部分数据。`,
Case: "select name from tbl where id < 1000 order by rand(number)", Case: "select name from tbl where id < 1000 order by rand(number)",
Func: (*Query4Audit).RuleOrderByRand, Func: (*Query4Audit).RuleOrderByRand,
}, },
...@@ -284,64 +284,64 @@ func init() { ...@@ -284,64 +284,64 @@ func init() {
"CLA.003": { "CLA.003": {
Item: "CLA.003", Item: "CLA.003",
Severity: "L2", Severity: "L2",
Summary: "不建议使用带OFFSET的LIMIT查询", Summary: "不建议使用带 OFFSET 的LIMIT 查询",
Content: `使用LIMIT和OFFSET对结果集分页的复杂度是O(n^2),并且会随着数据增大而导致性能问题。采用“书签”扫描的方法实现分页效率更高。`, Content: `使用 LIMIT 和 OFFSET 对结果集分页的复杂度是 O(n^2),并且会随着数据增大而导致性能问题。采用“书签”扫描的方法实现分页效率更高。`,
Case: "select c1,c2 from tbl where name=xx order by number limit 1 offset 20", Case: "select c1,c2 from tbl where name=xx order by number limit 1 offset 20",
Func: (*Query4Audit).RuleOffsetLimit, Func: (*Query4Audit).RuleOffsetLimit,
}, },
"CLA.004": { "CLA.004": {
Item: "CLA.004", Item: "CLA.004",
Severity: "L2", Severity: "L2",
Summary: "不建议对常量进行GROUP BY", Summary: "不建议对常量进行 GROUP BY",
Content: `GROUP BY 1 表示按第一列进行GROUP BY。如果在GROUP BY子句中使用数字,而不是表达式或列名称,当查询列顺序改变时,可能会导致问题。`, Content: `GROUP BY 1 表示按第一列进行 GROUP BY。如果在 GROUP BY 子句中使用数字,而不是表达式或列名称,当查询列顺序改变时,可能会导致问题。`,
Case: "select col1,col2 from tbl group by 1", Case: "select col1,col2 from tbl group by 1",
Func: (*Query4Audit).RuleGroupByConst, Func: (*Query4Audit).RuleGroupByConst,
}, },
"CLA.005": { "CLA.005": {
Item: "CLA.005", Item: "CLA.005",
Severity: "L2", Severity: "L2",
Summary: "ORDER BY常数列没有任何意义", Summary: "ORDER BY 常数列没有任何意义",
Content: `SQL逻辑上可能存在错误; 最多只是一个无用的操作,不会更改查询结果。`, Content: `SQL 逻辑上可能存在错误; 最多只是一个无用的操作,不会更改查询结果。`,
Case: "select id from test where id=1 order by id", Case: "select id from test where id=1 order by id",
Func: (*Query4Audit).RuleOrderByConst, Func: (*Query4Audit).RuleOrderByConst,
}, },
"CLA.006": { "CLA.006": {
Item: "CLA.006", Item: "CLA.006",
Severity: "L4", Severity: "L4",
Summary: "在不同的表中GROUP BY或ORDER BY", Summary: "在不同的表中 GROUP BY 或 ORDER BY",
Content: `这将强制使用临时表和filesort,可能产生巨大性能隐患,并且可能消耗大量内存和磁盘上的临时空间。`, Content: `这将强制使用临时表和 filesort,可能产生巨大性能隐患,并且可能消耗大量内存和磁盘上的临时空间。`,
Case: "select tb1.col, tb2.col from tb1, tb2 where id=1 group by tb1.col, tb2.col", Case: "select tb1.col, tb2.col from tb1, tb2 where id=1 group by tb1.col, tb2.col",
Func: (*Query4Audit).RuleDiffGroupByOrderBy, Func: (*Query4Audit).RuleDiffGroupByOrderBy,
}, },
"CLA.007": { "CLA.007": {
Item: "CLA.007", Item: "CLA.007",
Severity: "L2", Severity: "L2",
Summary: "ORDER BY语句对多个不同条件使用不同方向的排序无法使用索引", Summary: "ORDER BY 语句对多个不同条件使用不同方向的排序无法使用索引",
Content: `ORDER BY子句中的所有表达式必须按统一的ASC或DESC方向排序,以便利用索引。`, Content: `ORDER BY 子句中的所有表达式必须按统一的 ASC 或 DESC 方向排序,以便利用索引。`,
Case: "select c1,c2,c3 from t1 where c1='foo' order by c2 desc, c3 asc", Case: "select c1,c2,c3 from t1 where c1='foo' order by c2 desc, c3 asc",
Func: (*Query4Audit).RuleMixOrderBy, Func: (*Query4Audit).RuleMixOrderBy,
}, },
"CLA.008": { "CLA.008": {
Item: "CLA.008", Item: "CLA.008",
Severity: "L2", Severity: "L2",
Summary: "请为GROUP BY显示添加ORDER BY条件", Summary: "请为 GROUP BY 显示添加 ORDER BY 条件",
Content: `默认MySQL会对'GROUP BY col1, col2, ...'请求按如下顺序排序'ORDER BY col1, col2, ...'。如果GROUP BY语句不指定ORDER BY条件会导致无谓的排序产生,如果不需要排序建议添加'ORDER BY NULL'。`, Content: `默认 MySQL 会对 'GROUP BY col1, col2, ...' 请求按如下顺序排序 'ORDER BY col1, col2, ...'。如果 GROUP BY 语句不指定 ORDER BY 条件会导致无谓的排序产生,如果不需要排序建议添加 'ORDER BY NULL'。`,
Case: "select c1,c2,c3 from t1 where c1='foo' group by c2", Case: "select c1,c2,c3 from t1 where c1='foo' group by c2",
Func: (*Query4Audit).RuleExplicitOrderBy, Func: (*Query4Audit).RuleExplicitOrderBy,
}, },
"CLA.009": { "CLA.009": {
Item: "CLA.009", Item: "CLA.009",
Severity: "L2", Severity: "L2",
Summary: "ORDER BY的条件为表达式", Summary: "ORDER BY 的条件为表达式",
Content: `当ORDER BY条件为表达式或函数时会使用到临时表,如果在未指定WHERE或WHERE条件返回的结果集较大时性能会很差。`, Content: `当 ORDER BY 条件为表达式或函数时会使用到临时表,如果在未指定 WHERE 或 WHERE 条件返回的结果集较大时性能会很差。`,
Case: "select description from film where title ='ACADEMY DINOSAUR' order by length-language_id;", Case: "select description from film where title ='ACADEMY DINOSAUR' order by length-language_id;",
Func: (*Query4Audit).RuleOrderByExpr, Func: (*Query4Audit).RuleOrderByExpr,
}, },
"CLA.010": { "CLA.010": {
Item: "CLA.010", Item: "CLA.010",
Severity: "L2", Severity: "L2",
Summary: "GROUP BY的条件为表达式", Summary: "GROUP BY 的条件为表达式",
Content: `当GROUP BY条件为表达式或函数时会使用到临时表,如果在未指定WHERE或WHERE条件返回的结果集较大时性能会很差。`, Content: `当 GROUP BY 条件为表达式或函数时会使用到临时表,如果在未指定 WHERE 或 WHERE 条件返回的结果集较大时性能会很差。`,
Case: "select description from film where title ='ACADEMY DINOSAUR' GROUP BY length-language_id;", Case: "select description from film where title ='ACADEMY DINOSAUR' GROUP BY length-language_id;",
Func: (*Query4Audit).RuleGroupByExpr, Func: (*Query4Audit).RuleGroupByExpr,
}, },
...@@ -358,7 +358,7 @@ func init() { ...@@ -358,7 +358,7 @@ func init() {
Severity: "L2", Severity: "L2",
Summary: "将复杂的裹脚布式查询分解成几个简单的查询", Summary: "将复杂的裹脚布式查询分解成几个简单的查询",
Content: `SQL是一门极具表现力的语言,您可以在单个SQL查询或者单条语句中完成很多事情。但这并不意味着必须强制只使用一行代码,或者认为使用一行代码就搞定每个任务是个好主意。通过一个查询来获得所有结果的常见后果是得到了一个笛卡儿积。当查询中的两张表之间没有条件限制它们的关系时,就会发生这种情况。没有对应的限制而直接使用两张表进行联结查询,就会得到第一张表中的每一行和第二张表中的每一行的一个组合。每一个这样的组合就会成为结果集中的一行,最终您就会得到一个行数很多的结果集。重要的是要考虑这些查询很难编写、难以修改和难以调试。数据库查询请求的日益增加应该是预料之中的事。经理们想要更复杂的报告以及在用户界面上添加更多的字段。如果您的设计很复杂,并且是一个单一查询,要扩展它们就会很费时费力。不论对您还是项目来说,时间花在这些事情上面不值得。将复杂的意大利面条式查询分解成几个简单的查询。当您拆分一个复杂的SQL查询时,得到的结果可能是很多类似的查询,可能仅仅在数据类型上有所不同。编写所有的这些查询是很乏味的,因此,最好能够有个程序自动生成这些代码。SQL代码生成是一个很好的应用。尽管SQL支持用一行代码解决复杂的问题,但也别做不切实际的事情。`, Content: `SQL是一门极具表现力的语言,您可以在单个SQL查询或者单条语句中完成很多事情。但这并不意味着必须强制只使用一行代码,或者认为使用一行代码就搞定每个任务是个好主意。通过一个查询来获得所有结果的常见后果是得到了一个笛卡儿积。当查询中的两张表之间没有条件限制它们的关系时,就会发生这种情况。没有对应的限制而直接使用两张表进行联结查询,就会得到第一张表中的每一行和第二张表中的每一行的一个组合。每一个这样的组合就会成为结果集中的一行,最终您就会得到一个行数很多的结果集。重要的是要考虑这些查询很难编写、难以修改和难以调试。数据库查询请求的日益增加应该是预料之中的事。经理们想要更复杂的报告以及在用户界面上添加更多的字段。如果您的设计很复杂,并且是一个单一查询,要扩展它们就会很费时费力。不论对您还是项目来说,时间花在这些事情上面不值得。将复杂的意大利面条式查询分解成几个简单的查询。当您拆分一个复杂的SQL查询时,得到的结果可能是很多类似的查询,可能仅仅在数据类型上有所不同。编写所有的这些查询是很乏味的,因此,最好能够有个程序自动生成这些代码。SQL代码生成是一个很好的应用。尽管SQL支持用一行代码解决复杂的问题,但也别做不切实际的事情。`,
Case: "这是一条很长很长的SQL,案例略。", Case: "这是一条很长很长的 SQL,案例略。",
Func: (*Query4Audit).RuleSpaghettiQueryAlert, Func: (*Query4Audit).RuleSpaghettiQueryAlert,
}, },
/* /*
...@@ -387,31 +387,31 @@ func init() { ...@@ -387,31 +387,31 @@ func init() {
"CLA.013": { "CLA.013": {
Item: "CLA.013", Item: "CLA.013",
Severity: "L3", Severity: "L3",
Summary: "不建议使用HAVING子句", Summary: "不建议使用 HAVING 子句",
Content: `将查询的HAVING子句改写为WHERE中的查询条件,可以在查询处理期间使用索引。`, Content: `将查询的 HAVING 子句改写为 WHERE 中的查询条件,可以在查询处理期间使用索引。`,
Case: "SELECT s.c_id,count(s.c_id) FROM s where c = test GROUP BY s.c_id HAVING s.c_id <> '1660' AND s.c_id <> '2' order by s.c_id", Case: "SELECT s.c_id,count(s.c_id) FROM s where c = test GROUP BY s.c_id HAVING s.c_id <> '1660' AND s.c_id <> '2' order by s.c_id",
Func: (*Query4Audit).RuleHavingClause, Func: (*Query4Audit).RuleHavingClause,
}, },
"CLA.014": { "CLA.014": {
Item: "CLA.014", Item: "CLA.014",
Severity: "L2", Severity: "L2",
Summary: "删除全表时建议使用TRUNCATE替代DELETE", Summary: "删除全表时建议使用 TRUNCATE 替代 DELETE",
Content: `删除全表时建议使用TRUNCATE替代DELETE`, Content: `删除全表时建议使用 TRUNCATE 替代 DELETE`,
Case: "delete from tbl", Case: "delete from tbl",
Func: (*Query4Audit).RuleNoWhere, Func: (*Query4Audit).RuleNoWhere,
}, },
"CLA.015": { "CLA.015": {
Item: "CLA.015", Item: "CLA.015",
Severity: "L4", Severity: "L4",
Summary: "UPDATE未指定WHERE条件", Summary: "UPDATE 未指定 WHERE 条件",
Content: `UPDATE不指定WHERE条件一般是致命的,请您三思后行`, Content: `UPDATE 不指定 WHERE 条件一般是致命的,请您三思后行`,
Case: "update tbl set col=1", Case: "update tbl set col=1",
Func: (*Query4Audit).RuleNoWhere, Func: (*Query4Audit).RuleNoWhere,
}, },
"CLA.016": { "CLA.016": {
Item: "CLA.016", Item: "CLA.016",
Severity: "L2", Severity: "L2",
Summary: "不要UPDATE主键", Summary: "不要 UPDATE 主键",
Content: `主键是数据表中记录的唯一标识符,不建议频繁更新主键列,这将影响元数据统计信息进而影响正常的查询。`, Content: `主键是数据表中记录的唯一标识符,不建议频繁更新主键列,这将影响元数据统计信息进而影响正常的查询。`,
Case: "update tbl set col=1", Case: "update tbl set col=1",
Func: (*Query4Audit).RuleOK, // 该建议在indexAdvisor中给 Func: (*Query4Audit).RuleOK, // 该建议在indexAdvisor中给
...@@ -420,31 +420,31 @@ func init() { ...@@ -420,31 +420,31 @@ func init() {
Item: "CLA.017", Item: "CLA.017",
Severity: "L2", Severity: "L2",
Summary: "不建议使用存储过程、视图、触发器、临时表等", Summary: "不建议使用存储过程、视图、触发器、临时表等",
Content: `这些功能的使用在一定程度上会使得程序难以调试和拓展,更没有移植性,且会极大的增加出现BUG的概率。`, Content: `这些功能的使用在一定程度上会使得程序难以调试和拓展,更没有移植性,且会极大的增加出现 BUG 的概率。`,
Case: "CREATE VIEW v_today (today) AS SELECT CURRENT_DATE;", Case: "CREATE VIEW v_today (today) AS SELECT CURRENT_DATE;",
Func: (*Query4Audit).RuleForbiddenSyntax, Func: (*Query4Audit).RuleForbiddenSyntax,
}, },
"COL.001": { "COL.001": {
Item: "COL.001", Item: "COL.001",
Severity: "L1", Severity: "L1",
Summary: "不建议使用SELECT * 类型查询", Summary: "不建议使用 SELECT * 类型查询",
Content: `当表结构变更时,使用*通配符选择所有列将导致查询的含义和行为会发生更改,可能导致查询返回更多的数据。`, Content: `当表结构变更时,使用 * 通配符选择所有列将导致查询的含义和行为会发生更改,可能导致查询返回更多的数据。`,
Case: "select * from tbl where id=1", Case: "select * from tbl where id=1",
Func: (*Query4Audit).RuleSelectStar, Func: (*Query4Audit).RuleSelectStar,
}, },
"COL.002": { "COL.002": {
Item: "COL.002", Item: "COL.002",
Severity: "L2", Severity: "L2",
Summary: "INSERT未指定列名", Summary: "INSERT/REPLACE 未指定列名",
Content: `当表结构发生变更,如果INSERT或REPLACE请求不明确指定列名,请求的结果将会与预想的不同; 建议使用“INSERT INTO tbl(col1,col2)VALUES ...”代替。`, Content: `当表结构发生变更,如果 INSERT 或 REPLACE 请求不明确指定列名,请求的结果将会与预想的不同; 建议使用 “INSERT INTO tbl(col1,col2)VALUES ...” 代替。`,
Case: "insert into tbl values(1,'name')", Case: "insert into tbl values(1,'name')",
Func: (*Query4Audit).RuleInsertColDef, Func: (*Query4Audit).RuleInsertColDef,
}, },
"COL.003": { "COL.003": {
Item: "COL.003", Item: "COL.003",
Severity: "L2", Severity: "L2",
Summary: "建议修改自增ID为无符号类型", Summary: "建议修改自增 ID 为无符号类型",
Content: `建议修改自增ID为无符号类型`, Content: `建议修改自增 ID 为无符号类型`,
Case: "create table test(`id` int(11) NOT NULL AUTO_INCREMENT)", Case: "create table test(`id` int(11) NOT NULL AUTO_INCREMENT)",
Func: (*Query4Audit).RuleAutoIncUnsigned, Func: (*Query4Audit).RuleAutoIncUnsigned,
}, },
...@@ -452,7 +452,7 @@ func init() { ...@@ -452,7 +452,7 @@ func init() {
Item: "COL.004", Item: "COL.004",
Severity: "L1", Severity: "L1",
Summary: "请为列添加默认值", Summary: "请为列添加默认值",
Content: `请为列添加默认值,如果是ALTER操作,请不要忘记将原字段的默认值写上。字段无默认值,当表较大时无法在线变更表结构。`, Content: `请为列添加默认值,如果是 ALTER 操作,请不要忘记将原字段的默认值写上。字段无默认值,当表较大时无法在线变更表结构。`,
Case: "CREATE TABLE tbl (col int) ENGINE=InnoDB;", Case: "CREATE TABLE tbl (col int) ENGINE=InnoDB;",
Func: (*Query4Audit).RuleAddDefaultValue, Func: (*Query4Audit).RuleAddDefaultValue,
}, },
...@@ -475,7 +475,7 @@ func init() { ...@@ -475,7 +475,7 @@ func init() {
"COL.008": { "COL.008": {
Item: "COL.008", Item: "COL.008",
Severity: "L1", Severity: "L1",
Summary: "可使用VARCHAR代替CHAR,VARBINARY代替BINARY", Summary: "可使用 VARCHAR 代替 CHAR, VARBINARY 代替 BINARY",
Content: `为首先变长字段存储空间小,可以节省存储空间。其次对于查询来说,在一个相对较小的字段内搜索效率显然要高些。`, Content: `为首先变长字段存储空间小,可以节省存储空间。其次对于查询来说,在一个相对较小的字段内搜索效率显然要高些。`,
Case: "create table t1(id int,name char(20),last_time date)", Case: "create table t1(id int,name char(20),last_time date)",
Func: (*Query4Audit).RuleVarcharVSChar, Func: (*Query4Audit).RuleVarcharVSChar,
...@@ -484,15 +484,15 @@ func init() { ...@@ -484,15 +484,15 @@ func init() {
Item: "COL.009", Item: "COL.009",
Severity: "L2", Severity: "L2",
Summary: "建议使用精确的数据类型", Summary: "建议使用精确的数据类型",
Content: `实际上,任何使用FLOAT、REAL或DOUBLE PRECISION数据类型的设计都有可能是反模式。大多数应用程序使用的浮点数的取值范围并不需要达到IEEE 754标准所定义的最大/最小区间。在计算总量时,非精确浮点数所积累的影响是严重的。使用SQL中的NUMERIC或DECIMAL类型来代替FLOAT及其类似的数据类型进行固定精度的小数存储。这些数据类型精确地根据您定义这一列时指定的精度来存储数据。尽可能不要使用浮点数。`, Content: `实际上,任何使用 FLOAT, REAL 或 DOUBLE PRECISION 数据类型的设计都有可能是反模式。大多数应用程序使用的浮点数的取值范围并不需要达到IEEE 754标准所定义的最大/最小区间。在计算总量时,非精确浮点数所积累的影响是严重的。使用 SQL 中的 NUMERIC 或 DECIMAL 类型来代替 FLOAT 及其类似的数据类型进行固定精度的小数存储。这些数据类型精确地根据您定义这一列时指定的精度来存储数据。尽可能不要使用浮点数。`,
Case: "CREATE TABLE tab2 (p_id BIGINT UNSIGNED NOT NULL,a_id BIGINT UNSIGNED NOT NULL,hours float not null,PRIMARY KEY (p_id, a_id))", Case: "CREATE TABLE tab2 (p_id BIGINT UNSIGNED NOT NULL,a_id BIGINT UNSIGNED NOT NULL,hours float not null,PRIMARY KEY (p_id, a_id))",
Func: (*Query4Audit).RuleImpreciseDataType, Func: (*Query4Audit).RuleImpreciseDataType,
}, },
"COL.010": { "COL.010": {
Item: "COL.010", Item: "COL.010",
Severity: "L2", Severity: "L2",
Summary: "不建议使用ENUM数据类型", Summary: "不建议使用 ENUM 数据类型",
Content: `ENUM定义了列中值的类型,使用字符串表示ENUM里的值时,实际存储在列中的数据是这些值在定义时的序数。因此,这列的数据是字节对齐的,当您进行一次排序查询时,结果是按照实际存储的序数值排序的,而不是按字符串值的字母顺序排序的。这可能不是您所希望的。没有什么语法支持从ENUM或者check约束中添加或删除一个值;您只能使用一个新的集合重新定义这一列。如果您打算废弃一个选项,您可能会为历史数据而烦恼。作为一种策略,改变元数据——也就是说,改变表和列的定义——应该是不常见的,并且要注意测试和质量保证。有一个更好的解决方案来约束一列中的可选值:创建一张检查表,每一行包含一个允许在列中出现的候选值;然后在引用新表的旧表上声明一个外键约束。`, Content: `ENUM 定义了列中值的类型,使用字符串表示 ENUM 里的值时,实际存储在列中的数据是这些值在定义时的序数。因此,这列的数据是字节对齐的,当您进行一次排序查询时,结果是按照实际存储的序数值排序的,而不是按字符串值的字母顺序排序的。这可能不是您所希望的。没有什么语法支持从 ENUM 或者 check 约束中添加或删除一个值;您只能使用一个新的集合重新定义这一列。如果您打算废弃一个选项,您可能会为历史数据而烦恼。作为一种策略,改变元数据——也就是说,改变表和列的定义——应该是不常见的,并且要注意测试和质量保证。有一个更好的解决方案来约束一列中的可选值:创建一张检查表,每一行包含一个允许在列中出现的候选值;然后在引用新表的旧表上声明一个外键约束。`,
Case: "create table tab1(status ENUM('new','in progress','fixed'))", Case: "create table tab1(status ENUM('new','in progress','fixed'))",
Func: (*Query4Audit).RuleValuesInDefinition, Func: (*Query4Audit).RuleValuesInDefinition,
}, },
...@@ -500,24 +500,24 @@ func init() { ...@@ -500,24 +500,24 @@ func init() {
"COL.011": { "COL.011": {
Item: "COL.011", Item: "COL.011",
Severity: "L0", Severity: "L0",
Summary: "当需要唯一约束时才使用NULL,仅当列不能有缺失值时才使用NOT NULL", Summary: "当需要唯一约束时才使用 NULL,仅当列不能有缺失值时才使用 NOT NULL",
Content: `NULL和0是不同的,10乘以NULL还是NULL。NULL和空字符串是不一样的。将一个字符串和标准SQL中的NULL联合起来的结果还是NULL。NULL和FALSE也是不同的。AND、OR和NOT这三个布尔操作如果涉及NULL,其结果也让很多人感到困惑。当您将一列声明为NOT NULL时,也就是说这列中的每一个值都必须存在且是有意义的。使用NULL来表示任意类型不存在的空值。 当您将一列声明为NOT NULL时,也就是说这列中的每一个值都必须存在且是有意义的。`, Content: `NULL 和0是不同的,10乘以 NULL 还是 NULL。NULL 和空字符串是不一样的。将一个字符串和标准 SQL 中的 NULL 联合起来的结果还是 NULL。NULL 和 FALSE 也是不同的。AND、OR 和 NOT 这三个布尔操作如果涉及 NULL,其结果也让很多人感到困惑。当您将一列声明为 NOT NULL 时,也就是说这列中的每一个值都必须存在且是有意义的。使用 NULL 来表示任意类型不存在的空值。 当您将一列声明为 NOT NULL 时,也就是说这列中的每一个值都必须存在且是有意义的。`,
Case: "select c1,c2,c3 from tbl where c4 is null or c4 <> 1", Case: "select c1,c2,c3 from tbl where c4 is null or c4 <> 1",
Func: (*Query4Audit).RuleNullUsage, Func: (*Query4Audit).RuleNullUsage,
}, },
"COL.012": { "COL.012": {
Item: "COL.012", Item: "COL.012",
Severity: "L5", Severity: "L5",
Summary: "BLOB和TEXT类型的字段不可设置为NULL", Summary: "BLOB 和 TEXT 类型的字段不可设置为 NULL",
Content: `BLOB和TEXT类型的字段不可设置为NULL`, Content: `BLOB 和 TEXT 类型的字段不可设置为 NULL`,
Case: "CREATE TABLE `tbl` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `c` longblob, PRIMARY KEY (`id`));", Case: "CREATE TABLE `tbl` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `c` longblob, PRIMARY KEY (`id`));",
Func: (*Query4Audit).RuleCantBeNull, Func: (*Query4Audit).RuleCantBeNull,
}, },
"COL.013": { "COL.013": {
Item: "COL.013", Item: "COL.013",
Severity: "L4", Severity: "L4",
Summary: "TIMESTAMP类型未设置默认值", Summary: "TIMESTAMP 类型未设置默认值",
Content: `TIMESTAMP类型未设置默认值`, Content: `TIMESTAMP 类型未设置默认值`,
Case: "CREATE TABLE tbl( `id` bigint not null, `create_time` timestamp);", Case: "CREATE TABLE tbl( `id` bigint not null, `create_time` timestamp);",
Func: (*Query4Audit).RuleTimestampDefault, Func: (*Query4Audit).RuleTimestampDefault,
}, },
...@@ -532,15 +532,15 @@ func init() { ...@@ -532,15 +532,15 @@ func init() {
"COL.015": { "COL.015": {
Item: "COL.015", Item: "COL.015",
Severity: "L4", Severity: "L4",
Summary: "BLOB类型的字段不可指定默认值", Summary: "BLOB 类型的字段不可指定默认值",
Content: `BLOB类型的字段不可指定默认值`, Content: `BLOB 类型的字段不可指定默认值`,
Case: "CREATE TABLE `tbl` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `c` blob NOT NULL DEFAULT '', PRIMARY KEY (`id`));", Case: "CREATE TABLE `tbl` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `c` blob NOT NULL DEFAULT '', PRIMARY KEY (`id`));",
Func: (*Query4Audit).RuleBlobDefaultValue, Func: (*Query4Audit).RuleBlobDefaultValue,
}, },
"COL.016": { "COL.016": {
Item: "COL.016", Item: "COL.016",
Severity: "L1", Severity: "L1",
Summary: "整型定义建议采用INT(10)或BIGINT(20)", Summary: "整型定义建议采用 INT(10) 或 BIGINT(20)",
Content: `INT(M) 在 integer 数据类型中,M 表示最大显示宽度。 在 INT(M) 中,M 的值跟 INT(M) 所占多少存储空间并无任何关系。 INT(3)、INT(4)、INT(8) 在磁盘上都是占用 4 bytes 的存储空间。`, 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));", Case: "CREATE TABLE tab (a INT(1));",
Func: (*Query4Audit).RuleIntPrecision, Func: (*Query4Audit).RuleIntPrecision,
...@@ -548,7 +548,7 @@ func init() { ...@@ -548,7 +548,7 @@ func init() {
"COL.017": { "COL.017": {
Item: "COL.017", Item: "COL.017",
Severity: "L2", Severity: "L2",
Summary: "varchar定义长度过长", 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));", Case: "CREATE TABLE tab (a varchar(3500));",
Func: (*Query4Audit).RuleVarcharLength, Func: (*Query4Audit).RuleVarcharLength,
...@@ -556,16 +556,16 @@ func init() { ...@@ -556,16 +556,16 @@ func init() {
"DIS.001": { "DIS.001": {
Item: "DIS.001", Item: "DIS.001",
Severity: "L1", Severity: "L1",
Summary: "消除不必要的DISTINCT条件", Summary: "消除不必要的 DISTINCT 条件",
Content: `太多DISTINCT条件是复杂的裹脚布式查询的症状。考虑将复杂查询分解成许多简单的查询,并减少DISTINCT条件的数量。如果主键列是列的结果集的一部分,则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 xing, name from B) as e WHERE e.country_id = c.country_id", 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",
Func: (*Query4Audit).RuleDistinctUsage, Func: (*Query4Audit).RuleDistinctUsage,
}, },
"DIS.002": { "DIS.002": {
Item: "DIS.002", Item: "DIS.002",
Severity: "L3", Severity: "L3",
Summary: "COUNT(DISTINCT)多列时结果可能和你预想的不同", Summary: "COUNT(DISTINCT) 多列时结果可能和你预想的不同",
Content: `COUNT(DISTINCT col)计算该列除NULL之外的不重复行数,注意COUNT(DISTINCT col, col2)如果其中一列全为NULL那么即使另一列有不同的值,也返回0。`, Content: `COUNT(DISTINCT col) 计算该列除NULL之外的不重复行数,注意 COUNT(DISTINCT col, col2) 如果其中一列全为 NULL 那么即使另一列有不同的值,也返回0。`,
Case: "SELECT COUNT(DISTINCT col, col2) FROM tbl;", Case: "SELECT COUNT(DISTINCT col, col2) FROM tbl;",
Func: (*Query4Audit).RuleCountDistinctMultiCol, Func: (*Query4Audit).RuleCountDistinctMultiCol,
}, },
...@@ -574,7 +574,7 @@ func init() { ...@@ -574,7 +574,7 @@ func init() {
"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,
...@@ -591,7 +591,7 @@ func init() { ...@@ -591,7 +591,7 @@ func init() {
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,
}, },
...@@ -599,39 +599,39 @@ func init() { ...@@ -599,39 +599,39 @@ func init() {
Item: "FUN.003", Item: "FUN.003",
Severity: "L3", Severity: "L3",
Summary: "使用了合并为可空列的字符串连接", Summary: "使用了合并为可空列的字符串连接",
Content: `在一些查询请求中,您需要强制让某一列或者某个表达式返回非NULL的值,从而让查询逻辑变得更简单,担忧不想将这个值存下来。使用COALESCE()函数来构造连接的表达式,这样即使是空值列也不会使整表达式变为NULL。`, Content: `在一些查询请求中,您需要强制让某一列或者某个表达式返回非 NULL 的值,从而让查询逻辑变得更简单,担忧不想将这个值存下来。使用 COALESCE() 函数来构造连接的表达式,这样即使是空值列也不会使整表达式变为 NULL。`,
Case: "select c1 || coalesce(' ' || c2 || ' ', ' ') || c3 as c from tbl", Case: "select c1 || coalesce(' ' || c2 || ' ', ' ') || c3 as c from tbl",
Func: (*Query4Audit).RuleStringConcatenation, Func: (*Query4Audit).RuleStringConcatenation,
}, },
"FUN.004": { "FUN.004": {
Item: "FUN.004", Item: "FUN.004",
Severity: "L4", Severity: "L4",
Summary: "不建议使用SYSDATE()函数", Summary: "不建议使用 SYSDATE() 函数",
Content: `SYSDATE()函数可能导致主从数据不一致,请使用NOW()函数替代SYSDATE()。`, Content: `SYSDATE() 函数可能导致主从数据不一致,请使用 NOW() 函数替代 SYSDATE()。`,
Case: "SELECT SYSDATE();", Case: "SELECT SYSDATE();",
Func: (*Query4Audit).RuleSysdate, Func: (*Query4Audit).RuleSysdate,
}, },
"FUN.005": { "FUN.005": {
Item: "FUN.005", Item: "FUN.005",
Severity: "L1", Severity: "L1",
Summary: "不建议使用COUNT(col)或COUNT(常量)", Summary: "不建议使用 COUNT(col) 或 COUNT(常量)",
Content: `不要使用COUNT(col)或COUNT(常量)来替代COUNT(*),COUNT(*)是SQL92定义的标准统计行数的方法,跟数据无关,跟NULL和非NULL也无关。`, Content: `不要使用 COUNT(col) 或 COUNT(常量) 来替代 COUNT(*), COUNT(*) 是 SQL92 定义的标准统计行数的方法,跟数据无关,跟 NULL 和非 NULL 也无关。`,
Case: "SELECT COUNT(1) FROM tbl;", Case: "SELECT COUNT(1) FROM tbl;",
Func: (*Query4Audit).RuleCountConst, Func: (*Query4Audit).RuleCountConst,
}, },
"FUN.006": { "FUN.006": {
Item: "FUN.006", Item: "FUN.006",
Severity: "L1", Severity: "L1",
Summary: "使用SUM(COL)时需注意NPE问题", Summary: "使用 SUM(COL) 时需注意 NPE 问题",
Content: `当某一列的值全是NULL时,COUNT(COL)的返回结果为0,但SUM(COL)的返回结果为NULL,因此使用SUM()时需注意NPE问题。可以使用如下方式来避免SUM的NPE问题: SELECT IF(ISNULL(SUM(COL)), 0, SUM(COL)) FROM tbl`, Content: `当某一列的值全是 NULL 时,COUNT(COL) 的返回结果为0,但 SUM(COL) 的返回结果为 NULL,因此使用 SUM() 时需注意 NPE 问题。可以使用如下方式来避免 SUM 的 NPE 问题: SELECT IF(ISNULL(SUM(COL)), 0, SUM(COL)) FROM tbl`,
Case: "SELECT SUM(COL) FROM tbl;", Case: "SELECT SUM(COL) FROM tbl;",
Func: (*Query4Audit).RuleSumNPE, Func: (*Query4Audit).RuleSumNPE,
}, },
"GRP.001": { "GRP.001": {
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中给
}, },
...@@ -639,7 +639,7 @@ func init() { ...@@ -639,7 +639,7 @@ func init() {
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,
}, },
...@@ -647,38 +647,38 @@ func init() { ...@@ -647,38 +647,38 @@ func init() {
Item: "JOI.002", Item: "JOI.002",
Severity: "L4", Severity: "L4",
Summary: "同一张表被连接两次", Summary: "同一张表被连接两次",
Content: `相同的表在FROM子句中至少出现两次,可以简化为对该表的单次访问。`, Content: `相同的表在 FROM 子句中至少出现两次,可以简化为对该表的单次访问。`,
Case: "select tb1.col from (tb1, tb2) join tb2 on tb1.id=tb.id where tb1.id=1", Case: "select tb1.col from (tb1, tb2) join tb2 on tb1.id=tb.id where tb1.id=1",
Func: (*Query4Audit).RuleDupJoin, Func: (*Query4Audit).RuleDupJoin,
}, },
"JOI.003": { "JOI.003": {
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
}, },
"JOI.004": { "JOI.004": {
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
}, },
"JOI.005": { "JOI.005": {
Item: "JOI.005", Item: "JOI.005",
Severity: "L2", Severity: "L2",
Summary: "减少JOIN的数量", Summary: "减少 JOIN 的数量",
Content: `太多的JOIN是复杂的裹脚布式查询的症状。考虑将复杂查询分解成许多简单的查询,并减少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", 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",
Func: (*Query4Audit).RuleReduceNumberOfJoin, Func: (*Query4Audit).RuleReduceNumberOfJoin,
}, },
"JOI.006": { "JOI.006": {
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,
...@@ -687,7 +687,7 @@ func init() { ...@@ -687,7 +687,7 @@ func init() {
Item: "JOI.007", Item: "JOI.007",
Severity: "L4", Severity: "L4",
Summary: "不建议使用联表更新", Summary: "不建议使用联表更新",
Content: `当需要同时更新多张表时建议使用简单SQL,一条SQL只更新一张表,尽量不要将多张表的更新在同一条SQL中完成。`, Content: `当需要同时更新多张表时建议使用简单 SQL,一条 SQL 只更新一张表,尽量不要将多张表的更新在同一条 SQL 中完成。`,
Case: "UPDATE users u LEFT JOIN hobby h ON u.id = h.uid SET u.name = 'pianoboy' WHERE h.hobby = 'piano';", Case: "UPDATE users u LEFT JOIN hobby h ON u.id = h.uid SET u.name = 'pianoboy' WHERE h.hobby = 'piano';",
Func: (*Query4Audit).RuleMultiDeleteUpdate, Func: (*Query4Audit).RuleMultiDeleteUpdate,
}, },
...@@ -760,8 +760,8 @@ func init() { ...@@ -760,8 +760,8 @@ func init() {
"KEY.008": { "KEY.008": {
Item: "KEY.008", Item: "KEY.008",
Severity: "L4", Severity: "L4",
Summary: "ORDER BY多个列但排序方向不同时可能无法使用索引", Summary: "ORDER BY 多个列但排序方向不同时可能无法使用索引",
Content: `在MySQL 8.0之前当ORDER BY多个列指定的排序方向不同时将无法使用已经建立的索引。`, Content: `在 MySQL 8.0之前当 ORDER BY 多个列指定的排序方向不同时将无法使用已经建立的索引。`,
Case: "SELECT * FROM tbl ORDER BY a DESC, b ASC;", Case: "SELECT * FROM tbl ORDER BY a DESC, b ASC;",
Func: (*Query4Audit).RuleOrderByMultiDirection, Func: (*Query4Audit).RuleOrderByMultiDirection,
}, },
...@@ -777,14 +777,14 @@ func init() { ...@@ -777,14 +777,14 @@ func init() {
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,
}, },
"KWR.002": { "KWR.002": {
Item: "KWR.002", Item: "KWR.002",
Severity: "L2", Severity: "L2",
Summary: "不建议使用MySQL关键字做列名或表名", Summary: "不建议使用 MySQL 关键字做列名或表名",
Content: `当使用关键字做为列名或表名时程序需要对列名和表名进行转义,如果疏忽被将导致请求无法执行。`, Content: `当使用关键字做为列名或表名时程序需要对列名和表名进行转义,如果疏忽被将导致请求无法执行。`,
Case: "CREATE TABLE tbl ( `select` int )", Case: "CREATE TABLE tbl ( `select` int )",
Func: (*Query4Audit).RuleUseKeyWord, Func: (*Query4Audit).RuleUseKeyWord,
...@@ -800,16 +800,16 @@ func init() { ...@@ -800,16 +800,16 @@ func init() {
"LCK.001": { "LCK.001": {
Item: "LCK.001", Item: "LCK.001",
Severity: "L3", Severity: "L3",
Summary: "INSERT INTO xx SELECT加锁粒度较大请谨慎", Summary: "INSERT INTO xx SELECT 加锁粒度较大请谨慎",
Content: `INSERT INTO xx SELECT加锁粒度较大请谨慎`, Content: `INSERT INTO xx SELECT 加锁粒度较大请谨慎`,
Case: "INSERT INTO tbl SELECT * FROM tbl2;", Case: "INSERT INTO tbl SELECT * FROM tbl2;",
Func: (*Query4Audit).RuleInsertSelect, Func: (*Query4Audit).RuleInsertSelect,
}, },
"LCK.002": { "LCK.002": {
Item: "LCK.002", Item: "LCK.002",
Severity: "L3", Severity: "L3",
Summary: "请慎用INSERT ON DUPLICATE KEY UPDATE", Summary: "请慎用 INSERT ON DUPLICATE KEY UPDATE",
Content: `当主键为自增键时使用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;", Case: "INSERT INTO t1(a,b,c) VALUES (1,2,3) ON DUPLICATE KEY UPDATE c=c+1;",
Func: (*Query4Audit).RuleInsertOnDup, Func: (*Query4Audit).RuleInsertOnDup,
}, },
...@@ -817,7 +817,7 @@ func init() { ...@@ -817,7 +817,7 @@ func init() {
Item: "LIT.001", Item: "LIT.001",
Severity: "L2", Severity: "L2",
Summary: "用字符类型存储IP地址", Summary: "用字符类型存储IP地址",
Content: `字符串字面上看起来像IP地址,但不是INET_ATON()的参数,表示数据被存储为字符而不是整数。将IP地址存储为整数更为有效。`, Content: `字符串字面上看起来像IP地址,但不是 INET_ATON() 的参数,表示数据被存储为字符而不是整数。将IP地址存储为整数更为有效。`,
Case: "insert into tbl (IP,name) values('10.20.306.122','test')", Case: "insert into tbl (IP,name) values('10.20.306.122','test')",
Func: (*Query4Audit).RuleIPString, Func: (*Query4Audit).RuleIPString,
}, },
...@@ -833,14 +833,14 @@ func init() { ...@@ -833,14 +833,14 @@ func init() {
Item: "LIT.003", Item: "LIT.003",
Severity: "L3", Severity: "L3",
Summary: "一列中存储一系列相关数据的集合", Summary: "一列中存储一系列相关数据的集合",
Content: `将ID存储为一个列表,作为VARCHAR/TEXT列,这样能导致性能和数据完整性问题。查询这样的列需要使用模式匹配的表达式。使用逗号分隔的列表来做多表联结查询定位一行数据是极不优雅和耗时的。这将使验证ID更加困难。考虑一下,列表最多支持存放多少数据呢?将ID存储在一张单独的表中,代替使用多值属性,从而每个单独的属性值都可以占据一行。这样交叉表实现了两张表之间的多对多关系。这将更好地简化查询,也更有效地验证ID。`, Content: `将 ID 存储为一个列表,作为 VARCHAR/TEXT 列,这样能导致性能和数据完整性问题。查询这样的列需要使用模式匹配的表达式。使用逗号分隔的列表来做多表联结查询定位一行数据是极不优雅和耗时的。这将使验证 ID 更加困难。考虑一下,列表最多支持存放多少数据呢?将 ID 存储在一张单独的表中,代替使用多值属性,从而每个单独的属性值都可以占据一行。这样交叉表实现了两张表之间的多对多关系。这将更好地简化查询,也更有效地验证ID。`,
Case: "select c1,c2,c3,c4 from tab1 where col_id REGEXP '[[:<:]]12[[:>:]]'", Case: "select c1,c2,c3,c4 from tab1 where col_id REGEXP '[[:<:]]12[[:>:]]'",
Func: (*Query4Audit).RuleMultiValueAttribute, Func: (*Query4Audit).RuleMultiValueAttribute,
}, },
"LIT.004": { "LIT.004": {
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
...@@ -989,7 +989,7 @@ func init() { ...@@ -989,7 +989,7 @@ func init() {
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,
}, },
// TODO: 5.6有了semi join 还要把 in 转成e xists 么? // TODO: 5.6有了semi join 还要把 in 转成 exists 么?
// Use EXISTS instead of IN to check existence of data. // Use EXISTS instead of IN to check existence of data.
// http://www.winwire.com/25-tips-to-improve-sql-query-performance/ // http://www.winwire.com/25-tips-to-improve-sql-query-performance/
"SUB.004": { "SUB.004": {
......
...@@ -2,11 +2,11 @@ ...@@ -2,11 +2,11 @@
[toc] [toc]
## 建议使用AS关键字显示声明一个别名 ## 建议使用 AS 关键字显示声明一个别名
* **Item**:ALI.001 * **Item**:ALI.001
* **Severity**:L0 * **Severity**:L0
* **Content**:在列或表别名(如"tbl AS alias")中, 明确使用AS关键字比隐含别名(如"tbl alias")更易懂。 * **Content**:在列或表别名(如"tbl AS alias")中, 明确使用 AS 关键字比隐含别名(如"tbl alias")更易懂。
* **Case**: * **Case**:
```sql ```sql
...@@ -16,7 +16,7 @@ select name from tbl t1 where id < 1000 ...@@ -16,7 +16,7 @@ select name from tbl t1 where id < 1000
* **Item**:ALI.002 * **Item**:ALI.002
* **Severity**:L8 * **Severity**:L8
* **Content**:例: "SELECT tbl.\* col1, col2"上面这条SQL给列通配符设置了别名,这样的SQL可能存在逻辑错误。您可能意在查询col1, 但是代替它的是重命名的是tbl的最后一列。 * **Content**:例: "SELECT tbl.\* col1, col2"上面这条 SQL 给列通配符设置了别名,这样的SQL可能存在逻辑错误。您可能意在查询 col1, 但是代替它的是重命名的是 tbl 的最后一列。
* **Case**: * **Case**:
```sql ```sql
...@@ -36,17 +36,17 @@ select name from tbl as tbl where id < 1000 ...@@ -36,17 +36,17 @@ select name from tbl as tbl where id < 1000
* **Item**:ALT.001 * **Item**:ALT.001
* **Severity**:L4 * **Severity**:L4
* **Content**:很多初学者会将ALTER TABLE tbl\_name [DEFAULT] CHARACTER SET 'UTF8'误认为会修改所有字段的字符集,但实际上它只会影响后续新增的字段不会改表已有字段的字符集。如果想修改整张表所有字段的字符集建议使用ALTER TABLE tbl\_name CONVERT TO CHARACTER SET charset\_name; * **Content**:很多初学者会将 ALTER TABLE tbl\_name [DEFAULT] CHARACTER SET 'UTF8' 误认为会修改所有字段的字符集,但实际上它只会影响后续新增的字段不会改表已有字段的字符集。如果想修改整张表所有字段的字符集建议使用 ALTER TABLE tbl\_name CONVERT TO CHARACTER SET charset\_name;
* **Case**: * **Case**:
```sql ```sql
ALTER TABLE tbl_name CONVERT TO CHARACTER SET charset_name; ALTER TABLE tbl_name CONVERT TO CHARACTER SET charset_name;
``` ```
## 同一张表的多条ALTER请求建议合为一条 ## 同一张表的多条 ALTER 请求建议合为一条
* **Item**:ALT.002 * **Item**:ALT.002
* **Severity**:L2 * **Severity**:L2
* **Content**:每次表结构变更对线上服务都会产生影响,即使是能够通过在线工具进行调整也请尽量通过合并ALTER请求的试减少操作次数。 * **Content**:每次表结构变更对线上服务都会产生影响,即使是能够通过在线工具进行调整也请尽量通过合并 ALTER 请求的试减少操作次数。
* **Case**: * **Case**:
```sql ```sql
...@@ -62,11 +62,11 @@ ALTER TABLE tbl ADD COLUMN col int, ADD INDEX idx_col (`col`); ...@@ -62,11 +62,11 @@ ALTER TABLE tbl ADD COLUMN col int, ADD INDEX idx_col (`col`);
```sql ```sql
ALTER TABLE tbl DROP COLUMN col; ALTER TABLE tbl DROP COLUMN col;
``` ```
## 删除主键和外键为高危操作,操作前请与DBA确认影响 ## 删除主键和外键为高危操作,操作前请与 DBA 确认影响
* **Item**:ALT.004 * **Item**:ALT.004
* **Severity**:L0 * **Severity**:L0
* **Content**:主键和外键为关系型数据库中两种重要约束,删除已有约束会打破已有业务逻辑,操作前请业务开发与DBA确认影响,三思而行。 * **Content**:主键和外键为关系型数据库中两种重要约束,删除已有约束会打破已有业务逻辑,操作前请业务开发与 DBA 确认影响,三思而行。
* **Case**: * **Case**:
```sql ```sql
...@@ -76,17 +76,17 @@ ALTER TABLE tbl DROP PRIMARY KEY; ...@@ -76,17 +76,17 @@ ALTER TABLE tbl DROP PRIMARY KEY;
* **Item**:ARG.001 * **Item**:ARG.001
* **Severity**:L4 * **Severity**:L4
* **Content**:例如“%foo”,查询参数有一个前项通配符的情况无法使用已有索引。 * **Content**:例如 "%foo",查询参数有一个前项通配符的情况无法使用已有索引。
* **Case**: * **Case**:
```sql ```sql
select c1,c2,c3 from tbl where name like '%foo' select c1,c2,c3 from tbl where name like '%foo'
``` ```
## 没有通配符的LIKE查询 ## 没有通配符的 LIKE 查询
* **Item**:ARG.002 * **Item**:ARG.002
* **Severity**:L1 * **Severity**:L1
* **Content**:不包含通配符的LIKE查询可能存在逻辑错误,因为逻辑上它与等值查询相同。 * **Content**:不包含通配符的 LIKE 查询可能存在逻辑错误,因为逻辑上它与等值查询相同。
* **Case**: * **Case**:
```sql ```sql
...@@ -102,31 +102,31 @@ select c1,c2,c3 from tbl where name like 'foo' ...@@ -102,31 +102,31 @@ select c1,c2,c3 from tbl where name like 'foo'
```sql ```sql
SELECT * FROM sakila.film WHERE length >= '60'; SELECT * FROM sakila.film WHERE length >= '60';
``` ```
## IN (NULL)/NOT IN (NULL)永远非真 ## IN (NULL)/NOT IN (NULL) 永远非真
* **Item**:ARG.004 * **Item**:ARG.004
* **Severity**:L4 * **Severity**:L4
* **Content**:正确的作法是col IN ('val1', 'val2', 'val3') OR col IS NULL * **Content**:正确的作法是 col IN ('val1', 'val2', 'val3') OR col IS NULL
* **Case**: * **Case**:
```sql ```sql
SELECT * FROM tb WHERE col IN (NULL); SELECT * FROM tb WHERE col IN (NULL);
``` ```
## IN要慎用,元素过多会导致全表扫描 ## IN 要慎用,元素过多会导致全表扫描
* **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
select id from t where num in(1,2,3) select id from t where num in(1,2,3)
``` ```
## 应尽量避免在WHERE子句中对字段进行NULL值判断 ## 应尽量避免在 WHERE 子句中对字段进行 NULL 值判断
* **Item**:ARG.006 * **Item**:ARG.006
* **Severity**:L1 * **Severity**:L1
* **Content**:使用IS NULL或IS NOT NULL将可能导致引擎放弃使用索引而进行全表扫描,如:select id from t where num is null;可以在num上设置默认值0,确保表中num列没有null值,然后这样查询: select id from t where num=0; * **Content**:使用 IS NULL 或 IS NOT NULL 将可能导致引擎放弃使用索引而进行全表扫描,如:select id from t where num is null;可以在num上设置默认值0,确保表中 num 列没有 NULL 值,然后这样查询: select id from t where num=0;
* **Case**: * **Case**:
```sql ```sql
...@@ -136,7 +136,7 @@ select id from t where num is null ...@@ -136,7 +136,7 @@ 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
...@@ -146,7 +146,7 @@ select c_id,c2,c3 from tbl where c2 like 'test%' ...@@ -146,7 +146,7 @@ select c_id,c2,c3 from tbl where c2 like 'test%'
* **Item**:ARG.008 * **Item**:ARG.008
* **Severity**:L1 * **Severity**:L1
* **Content**:IN-list谓词可以用于索引检索,并且优化器可以对IN-list进行排序,以匹配索引的排序序列,从而获得更有效的检索。请注意,IN-list必须只包含常量,或在查询块执行期间保持常量的值,例如外引用。 * **Content**:IN-list 谓词可以用于索引检索,并且优化器可以对 IN-list 进行排序,以匹配索引的排序序列,从而获得更有效的检索。请注意,IN-list 必须只包含常量,或在查询块执行期间保持常量的值,例如外引用。
* **Case**: * **Case**:
```sql ```sql
...@@ -156,13 +156,13 @@ SELECT c1,c2,c3 FROM tbl WHERE c1 = 14 OR c1 = 17 ...@@ -156,13 +156,13 @@ 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
SELECT 'abc ' SELECT 'abc '
``` ```
## 不要使用hint,如sql\_no\_cache, force index, ignore key, straight join等 ## 不要使用 hint,如:sql\_no\_cache, force index, ignore key, straight join等
* **Item**:ARG.010 * **Item**:ARG.010
* **Severity**:L1 * **Severity**:L1
...@@ -182,101 +182,101 @@ SELECT * FROM t1 USE INDEX (i1) ORDER BY a; ...@@ -182,101 +182,101 @@ SELECT * FROM t1 USE INDEX (i1) ORDER BY a;
```sql ```sql
select id from t where num not in(1,2,3); select id from t where num not in(1,2,3);
``` ```
## 最外层SELECT未指定WHERE条件 ## 最外层 SELECT 未指定 WHERE 条件
* **Item**:CLA.001 * **Item**:CLA.001
* **Severity**:L4 * **Severity**:L4
* **Content**:SELECT语句没有WHERE子句,可能检查比预期更多的行(全表扫描)。对于SELECT COUNT(\*)类型的请求如果不要求精度,建议使用SHOW TABLE STATUS或EXPLAIN替代。 * **Content**:SELECT 语句没有 WHERE 子句,可能检查比预期更多的行(全表扫描)。对于 SELECT COUNT(\*) 类型的请求如果不要求精度,建议使用 SHOW TABLE STATUS 或 EXPLAIN 替代。
* **Case**: * **Case**:
```sql ```sql
select id from tbl select id from tbl
``` ```
## 不建议使用ORDER BY RAND() ## 不建议使用 ORDER BY RAND()
* **Item**:CLA.002 * **Item**:CLA.002
* **Severity**:L3 * **Severity**:L3
* **Content**:ORDER BY RAND()是从结果集中检索随机行的一种非常低效的方法,因为它会对整个结果进行排序并丢弃其大部分数据。 * **Content**:ORDER BY RAND() 是从结果集中检索随机行的一种非常低效的方法,因为它会对整个结果进行排序并丢弃其大部分数据。
* **Case**: * **Case**:
```sql ```sql
select name from tbl where id < 1000 order by rand(number) select name from tbl where id < 1000 order by rand(number)
``` ```
## 不建议使用带OFFSET的LIMIT查询 ## 不建议使用带 OFFSET 的LIMIT 查询
* **Item**:CLA.003 * **Item**:CLA.003
* **Severity**:L2 * **Severity**:L2
* **Content**:使用LIMIT和OFFSET对结果集分页的复杂度是O(n^2),并且会随着数据增大而导致性能问题。采用“书签”扫描的方法实现分页效率更高。 * **Content**:使用 LIMIT 和 OFFSET 对结果集分页的复杂度是 O(n^2),并且会随着数据增大而导致性能问题。采用“书签”扫描的方法实现分页效率更高。
* **Case**: * **Case**:
```sql ```sql
select c1,c2 from tbl where name=xx order by number limit 1 offset 20 select c1,c2 from tbl where name=xx order by number limit 1 offset 20
``` ```
## 不建议对常量进行GROUP BY ## 不建议对常量进行 GROUP BY
* **Item**:CLA.004 * **Item**:CLA.004
* **Severity**:L2 * **Severity**:L2
* **Content**:GROUP BY 1 表示按第一列进行GROUP BY。如果在GROUP BY子句中使用数字,而不是表达式或列名称,当查询列顺序改变时,可能会导致问题。 * **Content**:GROUP BY 1 表示按第一列进行 GROUP BY。如果在 GROUP BY 子句中使用数字,而不是表达式或列名称,当查询列顺序改变时,可能会导致问题。
* **Case**: * **Case**:
```sql ```sql
select col1,col2 from tbl group by 1 select col1,col2 from tbl group by 1
``` ```
## ORDER BY常数列没有任何意义 ## ORDER BY 常数列没有任何意义
* **Item**:CLA.005 * **Item**:CLA.005
* **Severity**:L2 * **Severity**:L2
* **Content**:SQL逻辑上可能存在错误; 最多只是一个无用的操作,不会更改查询结果。 * **Content**:SQL 逻辑上可能存在错误; 最多只是一个无用的操作,不会更改查询结果。
* **Case**: * **Case**:
```sql ```sql
select id from test where id=1 order by id select id from test where id=1 order by id
``` ```
## 在不同的表中GROUP BY或ORDER BY ## 在不同的表中 GROUP BY 或 ORDER BY
* **Item**:CLA.006 * **Item**:CLA.006
* **Severity**:L4 * **Severity**:L4
* **Content**:这将强制使用临时表和filesort,可能产生巨大性能隐患,并且可能消耗大量内存和磁盘上的临时空间。 * **Content**:这将强制使用临时表和 filesort,可能产生巨大性能隐患,并且可能消耗大量内存和磁盘上的临时空间。
* **Case**: * **Case**:
```sql ```sql
select tb1.col, tb2.col from tb1, tb2 where id=1 group by tb1.col, tb2.col select tb1.col, tb2.col from tb1, tb2 where id=1 group by tb1.col, tb2.col
``` ```
## ORDER BY语句对多个不同条件使用不同方向的排序无法使用索引 ## ORDER BY 语句对多个不同条件使用不同方向的排序无法使用索引
* **Item**:CLA.007 * **Item**:CLA.007
* **Severity**:L2 * **Severity**:L2
* **Content**:ORDER BY子句中的所有表达式必须按统一的ASC或DESC方向排序,以便利用索引。 * **Content**:ORDER BY 子句中的所有表达式必须按统一的 ASC 或 DESC 方向排序,以便利用索引。
* **Case**: * **Case**:
```sql ```sql
select c1,c2,c3 from t1 where c1='foo' order by c2 desc, c3 asc select c1,c2,c3 from t1 where c1='foo' order by c2 desc, c3 asc
``` ```
## 请为GROUP BY显示添加ORDER BY条件 ## 请为 GROUP BY 显示添加 ORDER BY 条件
* **Item**:CLA.008 * **Item**:CLA.008
* **Severity**:L2 * **Severity**:L2
* **Content**:默认MySQL会对'GROUP BY col1, col2, ...'请求按如下顺序排序'ORDER BY col1, col2, ...'。如果GROUP BY语句不指定ORDER BY条件会导致无谓的排序产生,如果不需要排序建议添加'ORDER BY NULL'。 * **Content**:默认 MySQL 会对 'GROUP BY col1, col2, ...' 请求按如下顺序排序 'ORDER BY col1, col2, ...'。如果 GROUP BY 语句不指定 ORDER BY 条件会导致无谓的排序产生,如果不需要排序建议添加 'ORDER BY NULL'。
* **Case**: * **Case**:
```sql ```sql
select c1,c2,c3 from t1 where c1='foo' group by c2 select c1,c2,c3 from t1 where c1='foo' group by c2
``` ```
## ORDER BY的条件为表达式 ## ORDER BY 的条件为表达式
* **Item**:CLA.009 * **Item**:CLA.009
* **Severity**:L2 * **Severity**:L2
* **Content**:当ORDER BY条件为表达式或函数时会使用到临时表,如果在未指定WHERE或WHERE条件返回的结果集较大时性能会很差。 * **Content**:当 ORDER BY 条件为表达式或函数时会使用到临时表,如果在未指定 WHERE 或 WHERE 条件返回的结果集较大时性能会很差。
* **Case**: * **Case**:
```sql ```sql
select description from film where title ='ACADEMY DINOSAUR' order by length-language_id; select description from film where title ='ACADEMY DINOSAUR' order by length-language_id;
``` ```
## GROUP BY的条件为表达式 ## GROUP BY 的条件为表达式
* **Item**:CLA.010 * **Item**:CLA.010
* **Severity**:L2 * **Severity**:L2
* **Content**:当GROUP BY条件为表达式或函数时会使用到临时表,如果在未指定WHERE或WHERE条件返回的结果集较大时性能会很差。 * **Content**:当 GROUP BY 条件为表达式或函数时会使用到临时表,如果在未指定 WHERE 或 WHERE 条件返回的结果集较大时性能会很差。
* **Case**: * **Case**:
```sql ```sql
...@@ -300,39 +300,39 @@ CREATE TABLE `test1` (`ID` bigint(20) NOT NULL AUTO_INCREMENT,`c1` varchar(128) ...@@ -300,39 +300,39 @@ CREATE TABLE `test1` (`ID` bigint(20) NOT NULL AUTO_INCREMENT,`c1` varchar(128)
* **Case**: * **Case**:
```sql ```sql
这是一条很长很长的SQL,案例略。 这是一条很长很长的 SQL,案例略。
``` ```
## 不建议使用HAVING子句 ## 不建议使用 HAVING 子句
* **Item**:CLA.013 * **Item**:CLA.013
* **Severity**:L3 * **Severity**:L3
* **Content**:将查询的HAVING子句改写为WHERE中的查询条件,可以在查询处理期间使用索引。 * **Content**:将查询的 HAVING 子句改写为 WHERE 中的查询条件,可以在查询处理期间使用索引。
* **Case**: * **Case**:
```sql ```sql
SELECT s.c_id,count(s.c_id) FROM s where c = test GROUP BY s.c_id HAVING s.c_id <> '1660' AND s.c_id <> '2' order by s.c_id SELECT s.c_id,count(s.c_id) FROM s where c = test GROUP BY s.c_id HAVING s.c_id <> '1660' AND s.c_id <> '2' order by s.c_id
``` ```
## 删除全表时建议使用TRUNCATE替代DELETE ## 删除全表时建议使用 TRUNCATE 替代 DELETE
* **Item**:CLA.014 * **Item**:CLA.014
* **Severity**:L2 * **Severity**:L2
* **Content**:删除全表时建议使用TRUNCATE替代DELETE * **Content**:删除全表时建议使用 TRUNCATE 替代 DELETE
* **Case**: * **Case**:
```sql ```sql
delete from tbl delete from tbl
``` ```
## UPDATE未指定WHERE条件 ## UPDATE 未指定 WHERE 条件
* **Item**:CLA.015 * **Item**:CLA.015
* **Severity**:L4 * **Severity**:L4
* **Content**:UPDATE不指定WHERE条件一般是致命的,请您三思后行 * **Content**:UPDATE 不指定 WHERE 条件一般是致命的,请您三思后行
* **Case**: * **Case**:
```sql ```sql
update tbl set col=1 update tbl set col=1
``` ```
## 不要UPDATE主键 ## 不要 UPDATE 主键
* **Item**:CLA.016 * **Item**:CLA.016
* **Severity**:L2 * **Severity**:L2
...@@ -346,37 +346,37 @@ update tbl set col=1 ...@@ -346,37 +346,37 @@ update tbl set col=1
* **Item**:CLA.017 * **Item**:CLA.017
* **Severity**:L2 * **Severity**:L2
* **Content**:这些功能的使用在一定程度上会使得程序难以调试和拓展,更没有移植性,且会极大的增加出现BUG的概率。 * **Content**:这些功能的使用在一定程度上会使得程序难以调试和拓展,更没有移植性,且会极大的增加出现 BUG 的概率。
* **Case**: * **Case**:
```sql ```sql
CREATE VIEW v_today (today) AS SELECT CURRENT_DATE; CREATE VIEW v_today (today) AS SELECT CURRENT_DATE;
``` ```
## 不建议使用SELECT \* 类型查询 ## 不建议使用 SELECT \* 类型查询
* **Item**:COL.001 * **Item**:COL.001
* **Severity**:L1 * **Severity**:L1
* **Content**:当表结构变更时,使用\*通配符选择所有列将导致查询的含义和行为会发生更改,可能导致查询返回更多的数据。 * **Content**:当表结构变更时,使用 \* 通配符选择所有列将导致查询的含义和行为会发生更改,可能导致查询返回更多的数据。
* **Case**: * **Case**:
```sql ```sql
select * from tbl where id=1 select * from tbl where id=1
``` ```
## INSERT未指定列名 ## INSERT/REPLACE 未指定列名
* **Item**:COL.002 * **Item**:COL.002
* **Severity**:L2 * **Severity**:L2
* **Content**:当表结构发生变更,如果INSERT或REPLACE请求不明确指定列名,请求的结果将会与预想的不同; 建议使用“INSERT INTO tbl(col1,col2)VALUES ...”代替。 * **Content**:当表结构发生变更,如果 INSERT 或 REPLACE 请求不明确指定列名,请求的结果将会与预想的不同; 建议使用 “INSERT INTO tbl(col1,col2)VALUES ...” 代替。
* **Case**: * **Case**:
```sql ```sql
insert into tbl values(1,'name') insert into tbl values(1,'name')
``` ```
## 建议修改自增ID为无符号类型 ## 建议修改自增 ID 为无符号类型
* **Item**:COL.003 * **Item**:COL.003
* **Severity**:L2 * **Severity**:L2
* **Content**:建议修改自增ID为无符号类型 * **Content**:建议修改自增 ID 为无符号类型
* **Case**: * **Case**:
```sql ```sql
...@@ -386,7 +386,7 @@ create table test(`id` int(11) NOT NULL AUTO_INCREMENT) ...@@ -386,7 +386,7 @@ create table test(`id` int(11) NOT NULL AUTO_INCREMENT)
* **Item**:COL.004 * **Item**:COL.004
* **Severity**:L1 * **Severity**:L1
* **Content**:请为列添加默认值,如果是ALTER操作,请不要忘记将原字段的默认值写上。字段无默认值,当表较大时无法在线变更表结构。 * **Content**:请为列添加默认值,如果是 ALTER 操作,请不要忘记将原字段的默认值写上。字段无默认值,当表较大时无法在线变更表结构。
* **Case**: * **Case**:
```sql ```sql
...@@ -412,7 +412,7 @@ CREATE TABLE tbl (col int) ENGINE=InnoDB; ...@@ -412,7 +412,7 @@ CREATE TABLE tbl (col int) ENGINE=InnoDB;
```sql ```sql
CREATE TABLE tbl ( cols ....); CREATE TABLE tbl ( cols ....);
``` ```
## 可使用VARCHAR代替CHAR,VARBINARY代替BINARY ## 可使用 VARCHAR 代替 CHAR, VARBINARY 代替 BINARY
* **Item**:COL.008 * **Item**:COL.008
* **Severity**:L1 * **Severity**:L1
...@@ -426,47 +426,47 @@ create table t1(id int,name char(20),last_time date) ...@@ -426,47 +426,47 @@ create table t1(id int,name char(20),last_time date)
* **Item**:COL.009 * **Item**:COL.009
* **Severity**:L2 * **Severity**:L2
* **Content**:实际上,任何使用FLOAT、REAL或DOUBLE PRECISION数据类型的设计都有可能是反模式。大多数应用程序使用的浮点数的取值范围并不需要达到IEEE 754标准所定义的最大/最小区间。在计算总量时,非精确浮点数所积累的影响是严重的。使用SQL中的NUMERIC或DECIMAL类型来代替FLOAT及其类似的数据类型进行固定精度的小数存储。这些数据类型精确地根据您定义这一列时指定的精度来存储数据。尽可能不要使用浮点数。 * **Content**:实际上,任何使用 FLOAT, REAL 或 DOUBLE PRECISION 数据类型的设计都有可能是反模式。大多数应用程序使用的浮点数的取值范围并不需要达到IEEE 754标准所定义的最大/最小区间。在计算总量时,非精确浮点数所积累的影响是严重的。使用 SQL 中的 NUMERIC 或 DECIMAL 类型来代替 FLOAT 及其类似的数据类型进行固定精度的小数存储。这些数据类型精确地根据您定义这一列时指定的精度来存储数据。尽可能不要使用浮点数。
* **Case**: * **Case**:
```sql ```sql
CREATE TABLE tab2 (p_id BIGINT UNSIGNED NOT NULL,a_id BIGINT UNSIGNED NOT NULL,hours float not null,PRIMARY KEY (p_id, a_id)) CREATE TABLE tab2 (p_id BIGINT UNSIGNED NOT NULL,a_id BIGINT UNSIGNED NOT NULL,hours float not null,PRIMARY KEY (p_id, a_id))
``` ```
## 不建议使用ENUM数据类型 ## 不建议使用 ENUM 数据类型
* **Item**:COL.010 * **Item**:COL.010
* **Severity**:L2 * **Severity**:L2
* **Content**:ENUM定义了列中值的类型,使用字符串表示ENUM里的值时,实际存储在列中的数据是这些值在定义时的序数。因此,这列的数据是字节对齐的,当您进行一次排序查询时,结果是按照实际存储的序数值排序的,而不是按字符串值的字母顺序排序的。这可能不是您所希望的。没有什么语法支持从ENUM或者check约束中添加或删除一个值;您只能使用一个新的集合重新定义这一列。如果您打算废弃一个选项,您可能会为历史数据而烦恼。作为一种策略,改变元数据——也就是说,改变表和列的定义——应该是不常见的,并且要注意测试和质量保证。有一个更好的解决方案来约束一列中的可选值:创建一张检查表,每一行包含一个允许在列中出现的候选值;然后在引用新表的旧表上声明一个外键约束。 * **Content**:ENUM 定义了列中值的类型,使用字符串表示 ENUM 里的值时,实际存储在列中的数据是这些值在定义时的序数。因此,这列的数据是字节对齐的,当您进行一次排序查询时,结果是按照实际存储的序数值排序的,而不是按字符串值的字母顺序排序的。这可能不是您所希望的。没有什么语法支持从 ENUM 或者 check 约束中添加或删除一个值;您只能使用一个新的集合重新定义这一列。如果您打算废弃一个选项,您可能会为历史数据而烦恼。作为一种策略,改变元数据——也就是说,改变表和列的定义——应该是不常见的,并且要注意测试和质量保证。有一个更好的解决方案来约束一列中的可选值:创建一张检查表,每一行包含一个允许在列中出现的候选值;然后在引用新表的旧表上声明一个外键约束。
* **Case**: * **Case**:
```sql ```sql
create table tab1(status ENUM('new','in progress','fixed')) create table tab1(status ENUM('new','in progress','fixed'))
``` ```
## 当需要唯一约束时才使用NULL,仅当列不能有缺失值时才使用NOT NULL ## 当需要唯一约束时才使用 NULL,仅当列不能有缺失值时才使用 NOT NULL
* **Item**:COL.011 * **Item**:COL.011
* **Severity**:L0 * **Severity**:L0
* **Content**:NULL和0是不同的,10乘以NULL还是NULL。NULL和空字符串是不一样的。将一个字符串和标准SQL中的NULL联合起来的结果还是NULL。NULL和FALSE也是不同的。AND、OR和NOT这三个布尔操作如果涉及NULL,其结果也让很多人感到困惑。当您将一列声明为NOT NULL时,也就是说这列中的每一个值都必须存在且是有意义的。使用NULL来表示任意类型不存在的空值。 当您将一列声明为NOT NULL时,也就是说这列中的每一个值都必须存在且是有意义的。 * **Content**:NULL 和0是不同的,10乘以 NULL 还是 NULL。NULL 和空字符串是不一样的。将一个字符串和标准 SQL 中的 NULL 联合起来的结果还是 NULL。NULL 和 FALSE 也是不同的。AND、OR 和 NOT 这三个布尔操作如果涉及 NULL,其结果也让很多人感到困惑。当您将一列声明为 NOT NULL 时,也就是说这列中的每一个值都必须存在且是有意义的。使用 NULL 来表示任意类型不存在的空值。 当您将一列声明为 NOT NULL 时,也就是说这列中的每一个值都必须存在且是有意义的。
* **Case**: * **Case**:
```sql ```sql
select c1,c2,c3 from tbl where c4 is null or c4 <> 1 select c1,c2,c3 from tbl where c4 is null or c4 <> 1
``` ```
## BLOB和TEXT类型的字段不可设置为NULL ## BLOB 和 TEXT 类型的字段不可设置为 NULL
* **Item**:COL.012 * **Item**:COL.012
* **Severity**:L5 * **Severity**:L5
* **Content**:BLOB和TEXT类型的字段不可设置为NULL * **Content**:BLOB 和 TEXT 类型的字段不可设置为 NULL
* **Case**: * **Case**:
```sql ```sql
CREATE TABLE `tbl` ( `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`));
``` ```
## TIMESTAMP类型未设置默认值 ## TIMESTAMP 类型未设置默认值
* **Item**:COL.013 * **Item**:COL.013
* **Severity**:L4 * **Severity**:L4
* **Content**:TIMESTAMP类型未设置默认值 * **Content**:TIMESTAMP 类型未设置默认值
* **Case**: * **Case**:
```sql ```sql
...@@ -482,17 +482,17 @@ CREATE TABLE tbl( `id` bigint not null, `create_time` timestamp); ...@@ -482,17 +482,17 @@ CREATE TABLE tbl( `id` bigint not null, `create_time` timestamp);
```sql ```sql
CREATE TABLE `tb2` ( `id` int(11) DEFAULT NULL, `col` char(10) CHARACTER SET utf8 DEFAULT NULL) CREATE TABLE `tb2` ( `id` int(11) DEFAULT NULL, `col` char(10) CHARACTER SET utf8 DEFAULT NULL)
``` ```
## BLOB类型的字段不可指定默认值 ## BLOB 类型的字段不可指定默认值
* **Item**:COL.015 * **Item**:COL.015
* **Severity**:L4 * **Severity**:L4
* **Content**:BLOB类型的字段不可指定默认值 * **Content**:BLOB 类型的字段不可指定默认值
* **Case**: * **Case**:
```sql ```sql
CREATE TABLE `tbl` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `c` blob NOT NULL DEFAULT '', PRIMARY KEY (`id`)); CREATE TABLE `tbl` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `c` blob NOT NULL DEFAULT '', PRIMARY KEY (`id`));
``` ```
## 整型定义建议采用INT(10)或BIGINT(20) ## 整型定义建议采用 INT(10) 或 BIGINT(20)
* **Item**:COL.016 * **Item**:COL.016
* **Severity**:L1 * **Severity**:L1
...@@ -502,7 +502,7 @@ CREATE TABLE `tbl` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `c` blob NOT ...@@ -502,7 +502,7 @@ CREATE TABLE `tbl` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `c` blob NOT
```sql ```sql
CREATE TABLE tab (a INT(1)); CREATE TABLE tab (a INT(1));
``` ```
## varchar定义长度过长 ## VARCHAR 定义长度过长
* **Item**:COL.017 * **Item**:COL.017
* **Severity**:L2 * **Severity**:L2
...@@ -512,7 +512,7 @@ CREATE TABLE tab (a INT(1)); ...@@ -512,7 +512,7 @@ CREATE TABLE tab (a INT(1));
```sql ```sql
CREATE TABLE tab (a varchar(3500)); CREATE TABLE tab (a varchar(3500));
``` ```
## 消除不必要的DISTINCT条件 ## 消除不必要的 DISTINCT 条件
* **Item**:DIS.001 * **Item**:DIS.001
* **Severity**:L1 * **Severity**:L1
...@@ -520,19 +520,19 @@ CREATE TABLE tab (a varchar(3500)); ...@@ -520,19 +520,19 @@ CREATE TABLE tab (a varchar(3500));
* **Case**: * **Case**:
```sql ```sql
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 xing, name from B) as e WHERE e.country_id = c.country_id 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
``` ```
## COUNT(DISTINCT)多列时结果可能和你预想的不同 ## COUNT(DISTINCT) 多列时结果可能和你预想的不同
* **Item**:DIS.002 * **Item**:DIS.002
* **Severity**:L3 * **Severity**:L3
* **Content**:COUNT(DISTINCT col)计算该列除NULL之外的不重复行数,注意COUNT(DISTINCT col, col2)如果其中一列全为NULL那么即使另一列有不同的值,也返回0。 * **Content**:COUNT(DISTINCT col) 计算该列除NULL之外的不重复行数,注意 COUNT(DISTINCT col, col2) 如果其中一列全为 NULL 那么即使另一列有不同的值,也返回0。
* **Case**: * **Case**:
```sql ```sql
SELECT COUNT(DISTINCT col, col2) FROM tbl; SELECT COUNT(DISTINCT col, col2) FROM tbl;
``` ```
## DISTINCT \*对有主键的表没有意义 ## DISTINCT \* 对有主键的表没有意义
* **Item**:DIS.003 * **Item**:DIS.003
* **Severity**:L3 * **Severity**:L3
...@@ -556,7 +556,7 @@ select id from t where substring(name,1,3)='abc' ...@@ -556,7 +556,7 @@ select id from t where substring(name,1,3)='abc'
* **Item**:FUN.002 * **Item**:FUN.002
* **Severity**:L1 * **Severity**:L1
* **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**: * **Case**:
```sql ```sql
...@@ -566,47 +566,47 @@ SELECT c3, COUNT(*) AS accounts FROM tab where c2 < 10000 GROUP BY c3 ORDER BY n ...@@ -566,47 +566,47 @@ SELECT c3, COUNT(*) AS accounts FROM tab where c2 < 10000 GROUP BY c3 ORDER BY n
* **Item**:FUN.003 * **Item**:FUN.003
* **Severity**:L3 * **Severity**:L3
* **Content**:在一些查询请求中,您需要强制让某一列或者某个表达式返回非NULL的值,从而让查询逻辑变得更简单,担忧不想将这个值存下来。使用COALESCE()函数来构造连接的表达式,这样即使是空值列也不会使整表达式变为NULL。 * **Content**:在一些查询请求中,您需要强制让某一列或者某个表达式返回非 NULL 的值,从而让查询逻辑变得更简单,担忧不想将这个值存下来。使用 COALESCE() 函数来构造连接的表达式,这样即使是空值列也不会使整表达式变为 NULL。
* **Case**: * **Case**:
```sql ```sql
select c1 || coalesce(' ' || c2 || ' ', ' ') || c3 as c from tbl select c1 || coalesce(' ' || c2 || ' ', ' ') || c3 as c from tbl
``` ```
## 不建议使用SYSDATE()函数 ## 不建议使用 SYSDATE() 函数
* **Item**:FUN.004 * **Item**:FUN.004
* **Severity**:L4 * **Severity**:L4
* **Content**:SYSDATE()函数可能导致主从数据不一致,请使用NOW()函数替代SYSDATE()。 * **Content**:SYSDATE() 函数可能导致主从数据不一致,请使用 NOW() 函数替代 SYSDATE()。
* **Case**: * **Case**:
```sql ```sql
SELECT SYSDATE(); SELECT SYSDATE();
``` ```
## 不建议使用COUNT(col)或COUNT(常量) ## 不建议使用 COUNT(col) 或 COUNT(常量)
* **Item**:FUN.005 * **Item**:FUN.005
* **Severity**:L1 * **Severity**:L1
* **Content**:不要使用COUNT(col)或COUNT(常量)来替代COUNT(\*),COUNT(\*)是SQL92定义的标准统计行数的方法,跟数据无关,跟NULL和非NULL也无关。 * **Content**:不要使用 COUNT(col) 或 COUNT(常量) 来替代 COUNT(\*), COUNT(\*) 是 SQL92 定义的标准统计行数的方法,跟数据无关,跟 NULL 和非 NULL 也无关。
* **Case**: * **Case**:
```sql ```sql
SELECT COUNT(1) FROM tbl; SELECT COUNT(1) FROM tbl;
``` ```
## 使用SUM(COL)时需注意NPE问题 ## 使用 SUM(COL) 时需注意 NPE 问题
* **Item**:FUN.006 * **Item**:FUN.006
* **Severity**:L1 * **Severity**:L1
* **Content**:当某一列的值全是NULL时,COUNT(COL)的返回结果为0,但SUM(COL)的返回结果为NULL,因此使用SUM()时需注意NPE问题。可以使用如下方式来避免SUM的NPE问题: SELECT IF(ISNULL(SUM(COL)), 0, SUM(COL)) FROM tbl * **Content**:当某一列的值全是 NULL 时,COUNT(COL) 的返回结果为0,但 SUM(COL) 的返回结果为 NULL,因此使用 SUM() 时需注意 NPE 问题。可以使用如下方式来避免 SUM 的 NPE 问题: SELECT IF(ISNULL(SUM(COL)), 0, SUM(COL)) FROM tbl
* **Case**: * **Case**:
```sql ```sql
SELECT SUM(COL) FROM tbl; SELECT SUM(COL) FROM tbl;
``` ```
## 不建议对等值查询列使用GROUP BY ## 不建议对等值查询列使用 GROUP BY
* **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
...@@ -616,7 +616,7 @@ select film_id, title from film where release_year='2006' group by release_year ...@@ -616,7 +616,7 @@ select film_id, title from film where release_year='2006' group by release_year
* **Item**:JOI.001 * **Item**:JOI.001
* **Severity**:L2 * **Severity**:L2
* **Content**:表连接的时候混用逗号和ANSI JOIN不便于人类理解,并且MySQL不同版本的表连接行为和优先级均有所不同,当MySQL版本变化后可能会引入错误。 * **Content**:表连接的时候混用逗号和 ANSI JOIN 不便于人类理解,并且MySQL不同版本的表连接行为和优先级均有所不同,当 MySQL 版本变化后可能会引入错误。
* **Case**: * **Case**:
```sql ```sql
...@@ -626,43 +626,43 @@ select c1,c2,c3 from t1,t2 join t3 on t1.c1=t2.c1,t1.c3=t3,c1 where id>1000 ...@@ -626,43 +626,43 @@ select c1,c2,c3 from t1,t2 join t3 on t1.c1=t2.c1,t1.c3=t3,c1 where id>1000
* **Item**:JOI.002 * **Item**:JOI.002
* **Severity**:L4 * **Severity**:L4
* **Content**:相同的表在FROM子句中至少出现两次,可以简化为对该表的单次访问。 * **Content**:相同的表在 FROM 子句中至少出现两次,可以简化为对该表的单次访问。
* **Case**: * **Case**:
```sql ```sql
select tb1.col from (tb1, tb2) join tb2 on tb1.id=tb.id where tb1.id=1 select tb1.col from (tb1, tb2) join tb2 on tb1.id=tb.id where tb1.id=1
``` ```
## OUTER JOIN失效 ## OUTER JOIN 失效
* **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
select c1,c2,c3 from t1 left outer join t2 using(c1) where t1.c2=2 and t2.c3=4 select c1,c2,c3 from t1 left outer join t2 using(c1) where t1.c2=2 and t2.c3=4
``` ```
## 不建议使用排它JOIN ## 不建议使用排它 JOIN
* **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
select c1,c2,c3 from t1 left outer join t2 on t1.c1=t2.c1 where t2.c2 is null select c1,c2,c3 from t1 left outer join t2 on t1.c1=t2.c1 where t2.c2 is null
``` ```
## 减少JOIN的数量 ## 减少 JOIN 的数量
* **Item**:JOI.005 * **Item**:JOI.005
* **Severity**:L2 * **Severity**:L2
* **Content**:太多的JOIN是复杂的裹脚布式查询的症状。考虑将复杂查询分解成许多简单的查询,并减少JOIN的数量。 * **Content**:太多的 JOIN 是复杂的裹脚布式查询的症状。考虑将复杂查询分解成许多简单的查询,并减少 JOIN 的数量。
* **Case**: * **Case**:
```sql ```sql
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 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
``` ```
## 将嵌套查询重写为JOIN通常会导致更高效的执行和更有效的优化 ## 将嵌套查询重写为 JOIN 通常会导致更高效的执行和更有效的优化
* **Item**:JOI.006 * **Item**:JOI.006
* **Severity**:L4 * **Severity**:L4
...@@ -676,7 +676,7 @@ SELECT s,p,d FROM tbl WHERE p.p_id = (SELECT s.p_id FROM tbl WHERE s.c_id = 1009 ...@@ -676,7 +676,7 @@ SELECT s,p,d FROM tbl WHERE p.p_id = (SELECT s.p_id FROM tbl WHERE s.c_id = 1009
* **Item**:JOI.007 * **Item**:JOI.007
* **Severity**:L4 * **Severity**:L4
* **Content**:当需要同时更新多张表时建议使用简单SQL,一条SQL只更新一张表,尽量不要将多张表的更新在同一条SQL中完成。 * **Content**:当需要同时更新多张表时建议使用简单 SQL,一条 SQL 只更新一张表,尽量不要将多张表的更新在同一条 SQL 中完成。
* **Case**: * **Case**:
```sql ```sql
...@@ -762,11 +762,11 @@ CREATE TABLE tbl ( a int, b int, c int, PRIMARY KEY(`a`,`b`,`c`)); ...@@ -762,11 +762,11 @@ CREATE TABLE tbl ( a int, b int, c int, PRIMARY KEY(`a`,`b`,`c`));
```sql ```sql
CREATE TABLE tbl (a int); CREATE TABLE tbl (a int);
``` ```
## ORDER BY多个列但排序方向不同时可能无法使用索引 ## ORDER BY 多个列但排序方向不同时可能无法使用索引
* **Item**:KEY.008 * **Item**:KEY.008
* **Severity**:L4 * **Severity**:L4
* **Content**:在MySQL 8.0之前当ORDER BY多个列指定的排序方向不同时将无法使用已经建立的索引。 * **Content**:在 MySQL 8.0之前当 ORDER BY 多个列指定的排序方向不同时将无法使用已经建立的索引。
* **Case**: * **Case**:
```sql ```sql
...@@ -786,13 +786,13 @@ CREATE UNIQUE INDEX part_of_name ON customer (name(10)); ...@@ -786,13 +786,13 @@ CREATE UNIQUE INDEX part_of_name ON customer (name(10));
* **Item**:KWR.001 * **Item**:KWR.001
* **Severity**:L2 * **Severity**:L2
* **Content**:因为SQL\_CALC\_FOUND\_ROWS不能很好地扩展,所以可能导致性能问题; 建议业务使用其他策略来替代SQL\_CALC\_FOUND\_ROWS提供的计数功能,比如:分页结果展示等。 * **Content**:因为 SQL\_CALC\_FOUND\_ROWS 不能很好地扩展,所以可能导致性能问题; 建议业务使用其他策略来替代 SQL\_CALC\_FOUND\_ROWS 提供的计数功能,比如:分页结果展示等。
* **Case**: * **Case**:
```sql ```sql
select SQL_CALC_FOUND_ROWS col from tbl where id>1000 select SQL_CALC_FOUND_ROWS col from tbl where id>1000
``` ```
## 不建议使用MySQL关键字做列名或表名 ## 不建议使用 MySQL 关键字做列名或表名
* **Item**:KWR.002 * **Item**:KWR.002
* **Severity**:L2 * **Severity**:L2
...@@ -812,21 +812,21 @@ CREATE TABLE tbl ( `select` int ) ...@@ -812,21 +812,21 @@ CREATE TABLE tbl ( `select` int )
```sql ```sql
CREATE TABLE tbl ( `books` int ) CREATE TABLE tbl ( `books` int )
``` ```
## INSERT INTO xx SELECT加锁粒度较大请谨慎 ## INSERT INTO xx SELECT 加锁粒度较大请谨慎
* **Item**:LCK.001 * **Item**:LCK.001
* **Severity**:L3 * **Severity**:L3
* **Content**:INSERT INTO xx SELECT加锁粒度较大请谨慎 * **Content**:INSERT INTO xx SELECT 加锁粒度较大请谨慎
* **Case**: * **Case**:
```sql ```sql
INSERT INTO tbl SELECT * FROM tbl2; INSERT INTO tbl SELECT * FROM tbl2;
``` ```
## 请慎用INSERT ON DUPLICATE KEY UPDATE ## 请慎用 INSERT ON DUPLICATE KEY UPDATE
* **Item**:LCK.002 * **Item**:LCK.002
* **Severity**:L3 * **Severity**:L3
* **Content**:当主键为自增键时使用INSERT ON DUPLICATE KEY UPDATE可能会导致主键出现大量不连续快速增长,导致主键快速溢出无法继续写入。极端情况下还有可能导致主从数据不一致。 * **Content**:当主键为自增键时使用 INSERT ON DUPLICATE KEY UPDATE 可能会导致主键出现大量不连续快速增长,导致主键快速溢出无法继续写入。极端情况下还有可能导致主从数据不一致。
* **Case**: * **Case**:
```sql ```sql
...@@ -836,7 +836,7 @@ INSERT INTO t1(a,b,c) VALUES (1,2,3) ON DUPLICATE KEY UPDATE c=c+1; ...@@ -836,7 +836,7 @@ INSERT INTO t1(a,b,c) VALUES (1,2,3) ON DUPLICATE KEY UPDATE c=c+1;
* **Item**:LIT.001 * **Item**:LIT.001
* **Severity**:L2 * **Severity**:L2
* **Content**:字符串字面上看起来像IP地址,但不是INET\_ATON()的参数,表示数据被存储为字符而不是整数。将IP地址存储为整数更为有效。 * **Content**:字符串字面上看起来像IP地址,但不是 INET\_ATON() 的参数,表示数据被存储为字符而不是整数。将IP地址存储为整数更为有效。
* **Case**: * **Case**:
```sql ```sql
...@@ -856,13 +856,13 @@ select col1,col2 from tbl where time < 2018-01-10 ...@@ -856,13 +856,13 @@ select col1,col2 from tbl where time < 2018-01-10
* **Item**:LIT.003 * **Item**:LIT.003
* **Severity**:L3 * **Severity**:L3
* **Content**:将ID存储为一个列表,作为VARCHAR/TEXT列,这样能导致性能和数据完整性问题。查询这样的列需要使用模式匹配的表达式。使用逗号分隔的列表来做多表联结查询定位一行数据是极不优雅和耗时的。这将使验证ID更加困难。考虑一下,列表最多支持存放多少数据呢?将ID存储在一张单独的表中,代替使用多值属性,从而每个单独的属性值都可以占据一行。这样交叉表实现了两张表之间的多对多关系。这将更好地简化查询,也更有效地验证ID。 * **Content**:将 ID 存储为一个列表,作为 VARCHAR/TEXT 列,这样能导致性能和数据完整性问题。查询这样的列需要使用模式匹配的表达式。使用逗号分隔的列表来做多表联结查询定位一行数据是极不优雅和耗时的。这将使验证 ID 更加困难。考虑一下,列表最多支持存放多少数据呢?将 ID 存储在一张单独的表中,代替使用多值属性,从而每个单独的属性值都可以占据一行。这样交叉表实现了两张表之间的多对多关系。这将更好地简化查询,也更有效地验证ID。
* **Case**: * **Case**:
```sql ```sql
select c1,c2,c3,c4 from tab1 where col_id REGEXP '[[:<:]]12[[:>:]]' select c1,c2,c3,c4 from tab1 where col_id REGEXP '[[:<:]]12[[:>:]]'
``` ```
## 请使用分号或已设定的DELIMITER结尾 ## 请使用分号或已设定的 DELIMITER 结尾
* **Item**:LIT.004 * **Item**:LIT.004
* **Severity**:L1 * **Severity**:L1
......
advisor.Rule{Item:"ALI.001", Severity:"L0", Summary:"建议使用AS关键字显示声明一个别名", Content:"在列或表别名(如\"tbl AS alias\")中, 明确使用AS关键字比隐含别名(如\"tbl alias\")更易懂。", Case:"select name from tbl t1 where id < 1000", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}} advisor.Rule{Item:"ALI.001", Severity:"L0", Summary:"建议使用 AS 关键字显示声明一个别名", Content:"在列或表别名(如\"tbl AS alias\")中, 明确使用 AS 关键字比隐含别名(如\"tbl alias\")更易懂。", Case:"select name from tbl t1 where id < 1000", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}
advisor.Rule{Item:"ALI.002", Severity:"L8", Summary:"不建议给列通配符'*'设置别名", Content:"例: \"SELECT tbl.* col1, col2\"上面这条SQL给列通配符设置了别名,这样的SQL可能存在逻辑错误。您可能意在查询col1, 但是代替它的是重命名的是tbl的最后一列。", Case:"select tbl.* as c1,c2,c3 from tbl where id < 1000", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}} advisor.Rule{Item:"ALI.002", Severity:"L8", Summary:"不建议给列通配符'*'设置别名", Content:"例: \"SELECT tbl.* col1, col2\"上面这条 SQL 给列通配符设置了别名,这样的SQL可能存在逻辑错误。您可能意在查询 col1, 但是代替它的是重命名的是 tbl 的最后一列。", Case:"select tbl.* as c1,c2,c3 from tbl where id < 1000", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}
advisor.Rule{Item:"ALI.003", Severity:"L1", Summary:"别名不要与表或列的名字相同", Content:"表或列的别名与其真实名称相同, 这样的别名会使得查询更难去分辨。", Case:"select name from tbl as tbl where id < 1000", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}} advisor.Rule{Item:"ALI.003", Severity:"L1", Summary:"别名不要与表或列的名字相同", Content:"表或列的别名与其真实名称相同, 这样的别名会使得查询更难去分辨。", Case:"select name from tbl as tbl where id < 1000", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}
advisor.Rule{Item:"ALT.001", Severity:"L4", Summary:"修改表的默认字符集不会改表各个字段的字符集", Content:"很多初学者会将ALTER TABLE tbl_name [DEFAULT] CHARACTER SET 'UTF8'误认为会修改所有字段的字符集,但实际上它只会影响后续新增的字段不会改表已有字段的字符集。如果想修改整张表所有字段的字符集建议使用ALTER TABLE tbl_name CONVERT TO CHARACTER SET charset_name;", Case:"ALTER TABLE tbl_name CONVERT TO CHARACTER SET charset_name;", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}} advisor.Rule{Item:"ALT.001", Severity:"L4", Summary:"修改表的默认字符集不会改表各个字段的字符集", Content:"很多初学者会将 ALTER TABLE tbl_name [DEFAULT] CHARACTER SET 'UTF8' 误认为会修改所有字段的字符集,但实际上它只会影响后续新增的字段不会改表已有字段的字符集。如果想修改整张表所有字段的字符集建议使用 ALTER TABLE tbl_name CONVERT TO CHARACTER SET charset_name;", Case:"ALTER TABLE tbl_name CONVERT TO CHARACTER SET charset_name;", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}
advisor.Rule{Item:"ALT.002", Severity:"L2", Summary:"同一张表的多条ALTER请求建议合为一条", Content:"每次表结构变更对线上服务都会产生影响,即使是能够通过在线工具进行调整也请尽量通过合并ALTER请求的试减少操作次数。", Case:"ALTER TABLE tbl ADD COLUMN col int, ADD INDEX idx_col (`col`);", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}} advisor.Rule{Item:"ALT.002", Severity:"L2", Summary:"同一张表的多条 ALTER 请求建议合为一条", Content:"每次表结构变更对线上服务都会产生影响,即使是能够通过在线工具进行调整也请尽量通过合并 ALTER 请求的试减少操作次数。", Case:"ALTER TABLE tbl ADD COLUMN col int, ADD INDEX idx_col (`col`);", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}
advisor.Rule{Item:"ALT.003", Severity:"L0", Summary:"删除列为高危操作,操作前请注意检查业务逻辑是否还有依赖", Content:"如业务逻辑依赖未完全消除,列被删除后可能导致数据无法写入或无法查询到已删除列数据导致程序异常的情况。这种情况下即使通过备份数据回滚也会丢失用户请求写入的数据。", Case:"ALTER TABLE tbl DROP COLUMN col;", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}} advisor.Rule{Item:"ALT.003", Severity:"L0", Summary:"删除列为高危操作,操作前请注意检查业务逻辑是否还有依赖", Content:"如业务逻辑依赖未完全消除,列被删除后可能导致数据无法写入或无法查询到已删除列数据导致程序异常的情况。这种情况下即使通过备份数据回滚也会丢失用户请求写入的数据。", Case:"ALTER TABLE tbl DROP COLUMN col;", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}
advisor.Rule{Item:"ALT.004", Severity:"L0", Summary:"删除主键和外键为高危操作,操作前请与DBA确认影响", Content:"主键和外键为关系型数据库中两种重要约束,删除已有约束会打破已有业务逻辑,操作前请业务开发与DBA确认影响,三思而行。", Case:"ALTER TABLE tbl DROP PRIMARY KEY;", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}} advisor.Rule{Item:"ALT.004", Severity:"L0", Summary:"删除主键和外键为高危操作,操作前请与 DBA 确认影响", Content:"主键和外键为关系型数据库中两种重要约束,删除已有约束会打破已有业务逻辑,操作前请业务开发与 DBA 确认影响,三思而行。", Case:"ALTER TABLE tbl DROP PRIMARY KEY;", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}
advisor.Rule{Item:"ARG.001", Severity:"L4", Summary:"不建议使用前项通配符查找", Content:"例如“%foo”,查询参数有一个前项通配符的情况无法使用已有索引。", Case:"select c1,c2,c3 from tbl where name like '%foo'", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}} advisor.Rule{Item:"ARG.001", Severity:"L4", Summary:"不建议使用前项通配符查找", Content:"例如 \"%foo\",查询参数有一个前项通配符的情况无法使用已有索引。", Case:"select c1,c2,c3 from tbl where name like '%foo'", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}
advisor.Rule{Item:"ARG.002", Severity:"L1", Summary:"没有通配符的LIKE查询", Content:"不包含通配符的LIKE查询可能存在逻辑错误,因为逻辑上它与等值查询相同。", Case:"select c1,c2,c3 from tbl where name like 'foo'", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}} advisor.Rule{Item:"ARG.002", Severity:"L1", Summary:"没有通配符的 LIKE 查询", Content:"不包含通配符的 LIKE 查询可能存在逻辑错误,因为逻辑上它与等值查询相同。", Case:"select c1,c2,c3 from tbl where name like 'foo'", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}
advisor.Rule{Item:"ARG.003", Severity:"L4", Summary:"参数比较包含隐式转换,无法使用索引", Content:"隐式类型转换有无法命中索引的风险,在高并发、大数据量的情况下,命不中索引带来的后果非常严重。", Case:"SELECT * FROM sakila.film WHERE length >= '60';", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}} advisor.Rule{Item:"ARG.003", Severity:"L4", Summary:"参数比较包含隐式转换,无法使用索引", Content:"隐式类型转换有无法命中索引的风险,在高并发、大数据量的情况下,命不中索引带来的后果非常严重。", Case:"SELECT * FROM sakila.film WHERE length >= '60';", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}
advisor.Rule{Item:"ARG.004", Severity:"L4", Summary:"IN (NULL)/NOT IN (NULL)永远非真", Content:"正确的作法是col IN ('val1', 'val2', 'val3') OR col IS NULL", Case:"SELECT * FROM tb WHERE col IN (NULL);", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}} advisor.Rule{Item:"ARG.004", Severity:"L4", Summary:"IN (NULL)/NOT IN (NULL) 永远非真", Content:"正确的作法是 col IN ('val1', 'val2', 'val3') OR col IS NULL", Case:"SELECT * FROM tb WHERE col IN (NULL);", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}
advisor.Rule{Item:"ARG.006", Severity:"L1", Summary:"应尽量避免在WHERE子句中对字段进行NULL值判断", Content:"使用IS NULL或IS NOT NULL将可能导致引擎放弃使用索引而进行全表扫描,如:select id from t where num is null;可以在num上设置默认值0,确保表中num列没有null值,然后这样查询: select id from t where num=0;", Case:"select id from t where num is null", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}} advisor.Rule{Item:"ARG.006", Severity:"L1", Summary:"应尽量避免在 WHERE 子句中对字段进行 NULL 值判断", Content:"使用 IS NULL 或 IS NOT NULL 将可能导致引擎放弃使用索引而进行全表扫描,如:select id from t where num is null;可以在num上设置默认值0,确保表中 num 列没有 NULL 值,然后这样查询: select id from t where num=0;", Case:"select id from t where num is null", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}
advisor.Rule{Item:"ARG.007", Severity:"L3", Summary:"避免使用模式匹配", Content:"性能问题是使用模式匹配操作符的最大缺点。使用LIKE或正则表达式进行模式匹配进行查询的另一个问题,是可能会返回意料之外的结果。最好的方案就是使用特殊的搜索引擎技术来替代SQL,比如 Apache Lucene。另一个可选方案是将结果保存起来从而减少重复的搜索开销。如果一定要使用SQL,请考虑在 MySQL 中使用像 FULLTEXT 索引这样的第三方扩展。但更广泛地说,您不一定要使用SQL来解决所有问题。", Case:"select c_id,c2,c3 from tbl where c2 like 'test%'", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}} advisor.Rule{Item:"ARG.007", Severity:"L3", Summary:"避免使用模式匹配", Content:"性能问题是使用模式匹配操作符的最大缺点。使用 LIKE 或正则表达式进行模式匹配进行查询的另一个问题,是可能会返回意料之外的结果。最好的方案就是使用特殊的搜索引擎技术来替代 SQL,比如 Apache Lucene。另一个可选方案是将结果保存起来从而减少重复的搜索开销。如果一定要使用SQL,请考虑在 MySQL 中使用像 FULLTEXT 索引这样的第三方扩展。但更广泛地说,您不一定要使用SQL来解决所有问题。", Case:"select c_id,c2,c3 from tbl where c2 like 'test%'", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}
advisor.Rule{Item:"ARG.008", Severity:"L1", Summary:"OR 查询索引列时请尽量使用 IN 谓词", Content:"IN-list谓词可以用于索引检索,并且优化器可以对IN-list进行排序,以匹配索引的排序序列,从而获得更有效的检索。请注意,IN-list必须只包含常量,或在查询块执行期间保持常量的值,例如外引用。", Case:"SELECT c1,c2,c3 FROM tbl WHERE c1 = 14 OR c1 = 17", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}} advisor.Rule{Item:"ARG.008", Severity:"L1", Summary:"OR 查询索引列时请尽量使用 IN 谓词", Content:"IN-list 谓词可以用于索引检索,并且优化器可以对 IN-list 进行排序,以匹配索引的排序序列,从而获得更有效的检索。请注意,IN-list 必须只包含常量,或在查询块执行期间保持常量的值,例如外引用。", Case:"SELECT c1,c2,c3 FROM tbl WHERE c1 = 14 OR c1 = 17", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}
advisor.Rule{Item:"ARG.009", Severity:"L1", Summary:"引号中的字符串开头或结尾包含空格", Content:"如果VARCHAR列的前后存在空格将可能引起逻辑问题,如在MySQL 5.5中 'a' 和 'a ' 可能会在查询中被认为是相同的值。", Case:"SELECT 'abc '", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}} advisor.Rule{Item:"ARG.009", Severity:"L1", Summary:"引号中的字符串开头或结尾包含空格", Content:"如果 VARCHAR 列的前后存在空格将可能引起逻辑问题,如在 MySQL 5.5中 'a' 和 'a ' 可能会在查询中被认为是相同的值。", Case:"SELECT 'abc '", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}
advisor.Rule{Item:"ARG.010", Severity:"L1", Summary:"不要使用hint,如sql_no_cache, force index, ignore key, straight join等", Content:"hint 是用来强制 SQL 按照某个执行计划来执行,但随着数据量变化我们无法保证自己当初的预判是正确的。", Case:"SELECT * FROM t1 USE INDEX (i1) ORDER BY a;", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}} advisor.Rule{Item:"ARG.010", Severity:"L1", Summary:"不要使用 hint,如:sql_no_cache, force index, ignore key, straight join等", Content:"hint 是用来强制 SQL 按照某个执行计划来执行,但随着数据量变化我们无法保证自己当初的预判是正确的。", Case:"SELECT * FROM t1 USE INDEX (i1) ORDER BY a;", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}
advisor.Rule{Item:"ARG.011", Severity:"L3", Summary:"不要使用负向查询,如:NOT IN/NOT LIKE", Content:"请尽量不要使用负向查询,这将导致全表扫描,对查询性能影响较大。", Case:"select id from t where num not in(1,2,3);", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}} advisor.Rule{Item:"ARG.011", Severity:"L3", Summary:"不要使用负向查询,如:NOT IN/NOT LIKE", Content:"请尽量不要使用负向查询,这将导致全表扫描,对查询性能影响较大。", Case:"select id from t where num not in(1,2,3);", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}
advisor.Rule{Item:"CLA.001", Severity:"L4", Summary:"最外层SELECT未指定WHERE条件", Content:"SELECT语句没有WHERE子句,可能检查比预期更多的行(全表扫描)。对于SELECT COUNT(*)类型的请求如果不要求精度,建议使用SHOW TABLE STATUS或EXPLAIN替代。", Case:"select id from tbl", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}} advisor.Rule{Item:"CLA.001", Severity:"L4", Summary:"最外层 SELECT 未指定 WHERE 条件", Content:"SELECT 语句没有 WHERE 子句,可能检查比预期更多的行(全表扫描)。对于 SELECT COUNT(*) 类型的请求如果不要求精度,建议使用 SHOW TABLE STATUS 或 EXPLAIN 替代。", Case:"select id from tbl", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}
advisor.Rule{Item:"CLA.002", Severity:"L3", Summary:"不建议使用ORDER BY RAND()", Content:"ORDER BY RAND()是从结果集中检索随机行的一种非常低效的方法,因为它会对整个结果进行排序并丢弃其大部分数据。", Case:"select name from tbl where id < 1000 order by rand(number)", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}} advisor.Rule{Item:"CLA.002", Severity:"L3", Summary:"不建议使用 ORDER BY RAND()", Content:"ORDER BY RAND() 是从结果集中检索随机行的一种非常低效的方法,因为它会对整个结果进行排序并丢弃其大部分数据。", Case:"select name from tbl where id < 1000 order by rand(number)", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}
advisor.Rule{Item:"CLA.003", Severity:"L2", Summary:"不建议使用带OFFSET的LIMIT查询", Content:"使用LIMIT和OFFSET对结果集分页的复杂度是O(n^2),并且会随着数据增大而导致性能问题。采用“书签”扫描的方法实现分页效率更高。", Case:"select c1,c2 from tbl where name=xx order by number limit 1 offset 20", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}} advisor.Rule{Item:"CLA.003", Severity:"L2", Summary:"不建议使用带 OFFSET 的LIMIT 查询", Content:"使用 LIMIT 和 OFFSET 对结果集分页的复杂度是 O(n^2),并且会随着数据增大而导致性能问题。采用“书签”扫描的方法实现分页效率更高。", Case:"select c1,c2 from tbl where name=xx order by number limit 1 offset 20", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}
advisor.Rule{Item:"CLA.004", Severity:"L2", Summary:"不建议对常量进行GROUP BY", Content:"GROUP BY 1 表示按第一列进行GROUP BY。如果在GROUP BY子句中使用数字,而不是表达式或列名称,当查询列顺序改变时,可能会导致问题。", Case:"select col1,col2 from tbl group by 1", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}} advisor.Rule{Item:"CLA.004", Severity:"L2", Summary:"不建议对常量进行 GROUP BY", Content:"GROUP BY 1 表示按第一列进行 GROUP BY。如果在 GROUP BY 子句中使用数字,而不是表达式或列名称,当查询列顺序改变时,可能会导致问题。", Case:"select col1,col2 from tbl group by 1", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}
advisor.Rule{Item:"CLA.005", Severity:"L2", Summary:"ORDER BY常数列没有任何意义", Content:"SQL逻辑上可能存在错误; 最多只是一个无用的操作,不会更改查询结果。", Case:"select id from test where id=1 order by id", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}} advisor.Rule{Item:"CLA.005", Severity:"L2", Summary:"ORDER BY 常数列没有任何意义", Content:"SQL 逻辑上可能存在错误; 最多只是一个无用的操作,不会更改查询结果。", Case:"select id from test where id=1 order by id", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}
advisor.Rule{Item:"CLA.006", Severity:"L4", Summary:"在不同的表中GROUP BY或ORDER BY", Content:"这将强制使用临时表和filesort,可能产生巨大性能隐患,并且可能消耗大量内存和磁盘上的临时空间。", Case:"select tb1.col, tb2.col from tb1, tb2 where id=1 group by tb1.col, tb2.col", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}} advisor.Rule{Item:"CLA.006", Severity:"L4", Summary:"在不同的表中 GROUP BY 或 ORDER BY", Content:"这将强制使用临时表和 filesort,可能产生巨大性能隐患,并且可能消耗大量内存和磁盘上的临时空间。", Case:"select tb1.col, tb2.col from tb1, tb2 where id=1 group by tb1.col, tb2.col", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}
advisor.Rule{Item:"CLA.007", Severity:"L2", Summary:"ORDER BY语句对多个不同条件使用不同方向的排序无法使用索引", Content:"ORDER BY子句中的所有表达式必须按统一的ASC或DESC方向排序,以便利用索引。", Case:"select c1,c2,c3 from t1 where c1='foo' order by c2 desc, c3 asc", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}} advisor.Rule{Item:"CLA.007", Severity:"L2", Summary:"ORDER BY 语句对多个不同条件使用不同方向的排序无法使用索引", Content:"ORDER BY 子句中的所有表达式必须按统一的 ASC 或 DESC 方向排序,以便利用索引。", Case:"select c1,c2,c3 from t1 where c1='foo' order by c2 desc, c3 asc", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}
advisor.Rule{Item:"CLA.008", Severity:"L2", Summary:"请为GROUP BY显示添加ORDER BY条件", Content:"默认MySQL会对'GROUP BY col1, col2, ...'请求按如下顺序排序'ORDER BY col1, col2, ...'。如果GROUP BY语句不指定ORDER BY条件会导致无谓的排序产生,如果不需要排序建议添加'ORDER BY NULL'。", Case:"select c1,c2,c3 from t1 where c1='foo' group by c2", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}} advisor.Rule{Item:"CLA.008", Severity:"L2", Summary:"请为 GROUP BY 显示添加 ORDER BY 条件", Content:"默认 MySQL 会对 'GROUP BY col1, col2, ...' 请求按如下顺序排序 'ORDER BY col1, col2, ...'。如果 GROUP BY 语句不指定 ORDER BY 条件会导致无谓的排序产生,如果不需要排序建议添加 'ORDER BY NULL'。", Case:"select c1,c2,c3 from t1 where c1='foo' group by c2", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}
advisor.Rule{Item:"CLA.009", Severity:"L2", Summary:"ORDER BY的条件为表达式", Content:"当ORDER BY条件为表达式或函数时会使用到临时表,如果在未指定WHERE或WHERE条件返回的结果集较大时性能会很差。", Case:"select description from film where title ='ACADEMY DINOSAUR' order by length-language_id;", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}} advisor.Rule{Item:"CLA.009", Severity:"L2", Summary:"ORDER BY 的条件为表达式", Content:"当 ORDER BY 条件为表达式或函数时会使用到临时表,如果在未指定 WHERE 或 WHERE 条件返回的结果集较大时性能会很差。", Case:"select description from film where title ='ACADEMY DINOSAUR' order by length-language_id;", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}
advisor.Rule{Item:"CLA.010", Severity:"L2", Summary:"GROUP BY的条件为表达式", Content:"当GROUP BY条件为表达式或函数时会使用到临时表,如果在未指定WHERE或WHERE条件返回的结果集较大时性能会很差。", Case:"select description from film where title ='ACADEMY DINOSAUR' GROUP BY length-language_id;", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}} advisor.Rule{Item:"CLA.010", Severity:"L2", Summary:"GROUP BY 的条件为表达式", Content:"当 GROUP BY 条件为表达式或函数时会使用到临时表,如果在未指定 WHERE 或 WHERE 条件返回的结果集较大时性能会很差。", Case:"select description from film where title ='ACADEMY DINOSAUR' GROUP BY length-language_id;", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}
advisor.Rule{Item:"CLA.011", Severity:"L1", Summary:"建议为表添加注释", Content:"为表添加注释能够使得表的意义更明确,从而为日后的维护带来极大的便利。", Case:"CREATE TABLE `test1` (`ID` bigint(20) NOT NULL AUTO_INCREMENT,`c1` varchar(128) DEFAULT NULL,PRIMARY KEY (`ID`)) ENGINE=InnoDB DEFAULT CHARSET=utf8", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}} advisor.Rule{Item:"CLA.011", Severity:"L1", Summary:"建议为表添加注释", Content:"为表添加注释能够使得表的意义更明确,从而为日后的维护带来极大的便利。", Case:"CREATE TABLE `test1` (`ID` bigint(20) NOT NULL AUTO_INCREMENT,`c1` varchar(128) DEFAULT NULL,PRIMARY KEY (`ID`)) ENGINE=InnoDB DEFAULT CHARSET=utf8", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}
advisor.Rule{Item:"CLA.012", Severity:"L2", Summary:"将复杂的裹脚布式查询分解成几个简单的查询", Content:"SQL是一门极具表现力的语言,您可以在单个SQL查询或者单条语句中完成很多事情。但这并不意味着必须强制只使用一行代码,或者认为使用一行代码就搞定每个任务是个好主意。通过一个查询来获得所有结果的常见后果是得到了一个笛卡儿积。当查询中的两张表之间没有条件限制它们的关系时,就会发生这种情况。没有对应的限制而直接使用两张表进行联结查询,就会得到第一张表中的每一行和第二张表中的每一行的一个组合。每一个这样的组合就会成为结果集中的一行,最终您就会得到一个行数很多的结果集。重要的是要考虑这些查询很难编写、难以修改和难以调试。数据库查询请求的日益增加应该是预料之中的事。经理们想要更复杂的报告以及在用户界面上添加更多的字段。如果您的设计很复杂,并且是一个单一查询,要扩展它们就会很费时费力。不论对您还是项目来说,时间花在这些事情上面不值得。将复杂的意大利面条式查询分解成几个简单的查询。当您拆分一个复杂的SQL查询时,得到的结果可能是很多类似的查询,可能仅仅在数据类型上有所不同。编写所有的这些查询是很乏味的,因此,最好能够有个程序自动生成这些代码。SQL代码生成是一个很好的应用。尽管SQL支持用一行代码解决复杂的问题,但也别做不切实际的事情。", Case:"这是一条很长很长的SQL,案例略。", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}} advisor.Rule{Item:"CLA.012", Severity:"L2", Summary:"将复杂的裹脚布式查询分解成几个简单的查询", Content:"SQL是一门极具表现力的语言,您可以在单个SQL查询或者单条语句中完成很多事情。但这并不意味着必须强制只使用一行代码,或者认为使用一行代码就搞定每个任务是个好主意。通过一个查询来获得所有结果的常见后果是得到了一个笛卡儿积。当查询中的两张表之间没有条件限制它们的关系时,就会发生这种情况。没有对应的限制而直接使用两张表进行联结查询,就会得到第一张表中的每一行和第二张表中的每一行的一个组合。每一个这样的组合就会成为结果集中的一行,最终您就会得到一个行数很多的结果集。重要的是要考虑这些查询很难编写、难以修改和难以调试。数据库查询请求的日益增加应该是预料之中的事。经理们想要更复杂的报告以及在用户界面上添加更多的字段。如果您的设计很复杂,并且是一个单一查询,要扩展它们就会很费时费力。不论对您还是项目来说,时间花在这些事情上面不值得。将复杂的意大利面条式查询分解成几个简单的查询。当您拆分一个复杂的SQL查询时,得到的结果可能是很多类似的查询,可能仅仅在数据类型上有所不同。编写所有的这些查询是很乏味的,因此,最好能够有个程序自动生成这些代码。SQL代码生成是一个很好的应用。尽管SQL支持用一行代码解决复杂的问题,但也别做不切实际的事情。", Case:"这是一条很长很长的 SQL,案例略。", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}
advisor.Rule{Item:"CLA.013", Severity:"L3", Summary:"不建议使用HAVING子句", Content:"将查询的HAVING子句改写为WHERE中的查询条件,可以在查询处理期间使用索引。", Case:"SELECT s.c_id,count(s.c_id) FROM s where c = test GROUP BY s.c_id HAVING s.c_id <> '1660' AND s.c_id <> '2' order by s.c_id", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}} advisor.Rule{Item:"CLA.013", Severity:"L3", Summary:"不建议使用 HAVING 子句", Content:"将查询的 HAVING 子句改写为 WHERE 中的查询条件,可以在查询处理期间使用索引。", Case:"SELECT s.c_id,count(s.c_id) FROM s where c = test GROUP BY s.c_id HAVING s.c_id <> '1660' AND s.c_id <> '2' order by s.c_id", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}
advisor.Rule{Item:"CLA.014", Severity:"L2", Summary:"删除全表时建议使用TRUNCATE替代DELETE", Content:"删除全表时建议使用TRUNCATE替代DELETE", Case:"delete from tbl", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}} advisor.Rule{Item:"CLA.014", Severity:"L2", Summary:"删除全表时建议使用 TRUNCATE 替代 DELETE", Content:"删除全表时建议使用 TRUNCATE 替代 DELETE", Case:"delete from tbl", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}
advisor.Rule{Item:"CLA.015", Severity:"L4", Summary:"UPDATE未指定WHERE条件", Content:"UPDATE不指定WHERE条件一般是致命的,请您三思后行", Case:"update tbl set col=1", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}} advisor.Rule{Item:"CLA.015", Severity:"L4", Summary:"UPDATE 未指定 WHERE 条件", Content:"UPDATE 不指定 WHERE 条件一般是致命的,请您三思后行", Case:"update tbl set col=1", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}
advisor.Rule{Item:"CLA.016", Severity:"L2", Summary:"不要UPDATE主键", Content:"主键是数据表中记录的唯一标识符,不建议频繁更新主键列,这将影响元数据统计信息进而影响正常的查询。", Case:"update tbl set col=1", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}} advisor.Rule{Item:"CLA.016", Severity:"L2", Summary:"不要 UPDATE 主键", Content:"主键是数据表中记录的唯一标识符,不建议频繁更新主键列,这将影响元数据统计信息进而影响正常的查询。", Case:"update tbl set col=1", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}
advisor.Rule{Item:"CLA.017", Severity:"L2", Summary:"不建议使用存储过程、视图、触发器、临时表等", Content:"这些功能的使用在一定程度上会使得程序难以调试和拓展,更没有移植性,且会极大的增加出现BUG的概率。", Case:"CREATE VIEW v_today (today) AS SELECT CURRENT_DATE;", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}} advisor.Rule{Item:"CLA.017", Severity:"L2", Summary:"不建议使用存储过程、视图、触发器、临时表等", Content:"这些功能的使用在一定程度上会使得程序难以调试和拓展,更没有移植性,且会极大的增加出现 BUG 的概率。", Case:"CREATE VIEW v_today (today) AS SELECT CURRENT_DATE;", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}
advisor.Rule{Item:"COL.001", Severity:"L1", Summary:"不建议使用SELECT * 类型查询", Content:"当表结构变更时,使用*通配符选择所有列将导致查询的含义和行为会发生更改,可能导致查询返回更多的数据。", Case:"select * from tbl where id=1", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}} advisor.Rule{Item:"COL.001", Severity:"L1", Summary:"不建议使用 SELECT * 类型查询", Content:"当表结构变更时,使用 * 通配符选择所有列将导致查询的含义和行为会发生更改,可能导致查询返回更多的数据。", Case:"select * from tbl where id=1", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}
advisor.Rule{Item:"COL.002", Severity:"L2", Summary:"INSERT未指定列名", Content:"当表结构发生变更,如果INSERT或REPLACE请求不明确指定列名,请求的结果将会与预想的不同; 建议使用“INSERT INTO tbl(col1,col2)VALUES ...”代替。", Case:"insert into tbl values(1,'name')", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}} advisor.Rule{Item:"COL.002", Severity:"L2", Summary:"INSERT/REPLACE 未指定列名", Content:"当表结构发生变更,如果 INSERT 或 REPLACE 请求不明确指定列名,请求的结果将会与预想的不同; 建议使用 “INSERT INTO tbl(col1,col2)VALUES ...” 代替。", Case:"insert into tbl values(1,'name')", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}
advisor.Rule{Item:"COL.003", Severity:"L2", Summary:"建议修改自增ID为无符号类型", Content:"建议修改自增ID为无符号类型", Case:"create table test(`id` int(11) NOT NULL AUTO_INCREMENT)", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}} advisor.Rule{Item:"COL.003", Severity:"L2", Summary:"建议修改自增 ID 为无符号类型", Content:"建议修改自增 ID 为无符号类型", Case:"create table test(`id` int(11) NOT NULL AUTO_INCREMENT)", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}
advisor.Rule{Item:"COL.004", Severity:"L1", Summary:"请为列添加默认值", Content:"请为列添加默认值,如果是ALTER操作,请不要忘记将原字段的默认值写上。字段无默认值,当表较大时无法在线变更表结构。", Case:"CREATE TABLE tbl (col int) ENGINE=InnoDB;", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}} advisor.Rule{Item:"COL.004", Severity:"L1", Summary:"请为列添加默认值", Content:"请为列添加默认值,如果是 ALTER 操作,请不要忘记将原字段的默认值写上。字段无默认值,当表较大时无法在线变更表结构。", Case:"CREATE TABLE tbl (col int) ENGINE=InnoDB;", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}
advisor.Rule{Item:"COL.005", Severity:"L1", Summary:"列未添加注释", Content:"建议对表中每个列添加注释,来明确每个列在表中的含义及作用。", Case:"CREATE TABLE tbl (col int) ENGINE=InnoDB;", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}} advisor.Rule{Item:"COL.005", Severity:"L1", Summary:"列未添加注释", Content:"建议对表中每个列添加注释,来明确每个列在表中的含义及作用。", Case:"CREATE TABLE tbl (col int) ENGINE=InnoDB;", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}
advisor.Rule{Item:"COL.006", Severity:"L3", Summary:"表中包含有太多的列", Content:"表中包含有太多的列", Case:"CREATE TABLE tbl ( cols ....);", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}} advisor.Rule{Item:"COL.006", Severity:"L3", Summary:"表中包含有太多的列", Content:"表中包含有太多的列", Case:"CREATE TABLE tbl ( cols ....);", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}
advisor.Rule{Item:"COL.008", Severity:"L1", Summary:"可使用VARCHAR代替CHAR,VARBINARY代替BINARY", Content:"为首先变长字段存储空间小,可以节省存储空间。其次对于查询来说,在一个相对较小的字段内搜索效率显然要高些。", Case:"create table t1(id int,name char(20),last_time date)", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}} advisor.Rule{Item:"COL.008", Severity:"L1", Summary:"可使用 VARCHAR 代替 CHAR, VARBINARY 代替 BINARY", Content:"为首先变长字段存储空间小,可以节省存储空间。其次对于查询来说,在一个相对较小的字段内搜索效率显然要高些。", Case:"create table t1(id int,name char(20),last_time date)", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}
advisor.Rule{Item:"COL.009", Severity:"L2", Summary:"建议使用精确的数据类型", Content:"实际上,任何使用FLOAT、REAL或DOUBLE PRECISION数据类型的设计都有可能是反模式。大多数应用程序使用的浮点数的取值范围并不需要达到IEEE 754标准所定义的最大/最小区间。在计算总量时,非精确浮点数所积累的影响是严重的。使用SQL中的NUMERIC或DECIMAL类型来代替FLOAT及其类似的数据类型进行固定精度的小数存储。这些数据类型精确地根据您定义这一列时指定的精度来存储数据。尽可能不要使用浮点数。", Case:"CREATE TABLE tab2 (p_id BIGINT UNSIGNED NOT NULL,a_id BIGINT UNSIGNED NOT NULL,hours float not null,PRIMARY KEY (p_id, a_id))", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}} advisor.Rule{Item:"COL.009", Severity:"L2", Summary:"建议使用精确的数据类型", Content:"实际上,任何使用 FLOAT, REAL 或 DOUBLE PRECISION 数据类型的设计都有可能是反模式。大多数应用程序使用的浮点数的取值范围并不需要达到IEEE 754标准所定义的最大/最小区间。在计算总量时,非精确浮点数所积累的影响是严重的。使用 SQL 中的 NUMERIC 或 DECIMAL 类型来代替 FLOAT 及其类似的数据类型进行固定精度的小数存储。这些数据类型精确地根据您定义这一列时指定的精度来存储数据。尽可能不要使用浮点数。", Case:"CREATE TABLE tab2 (p_id BIGINT UNSIGNED NOT NULL,a_id BIGINT UNSIGNED NOT NULL,hours float not null,PRIMARY KEY (p_id, a_id))", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}
advisor.Rule{Item:"COL.010", Severity:"L2", Summary:"不建议使用ENUM数据类型", Content:"ENUM定义了列中值的类型,使用字符串表示ENUM里的值时,实际存储在列中的数据是这些值在定义时的序数。因此,这列的数据是字节对齐的,当您进行一次排序查询时,结果是按照实际存储的序数值排序的,而不是按字符串值的字母顺序排序的。这可能不是您所希望的。没有什么语法支持从ENUM或者check约束中添加或删除一个值;您只能使用一个新的集合重新定义这一列。如果您打算废弃一个选项,您可能会为历史数据而烦恼。作为一种策略,改变元数据——也就是说,改变表和列的定义——应该是不常见的,并且要注意测试和质量保证。有一个更好的解决方案来约束一列中的可选值:创建一张检查表,每一行包含一个允许在列中出现的候选值;然后在引用新表的旧表上声明一个外键约束。", Case:"create table tab1(status ENUM('new','in progress','fixed'))", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}} advisor.Rule{Item:"COL.010", Severity:"L2", Summary:"不建议使用 ENUM 数据类型", Content:"ENUM 定义了列中值的类型,使用字符串表示 ENUM 里的值时,实际存储在列中的数据是这些值在定义时的序数。因此,这列的数据是字节对齐的,当您进行一次排序查询时,结果是按照实际存储的序数值排序的,而不是按字符串值的字母顺序排序的。这可能不是您所希望的。没有什么语法支持从 ENUM 或者 check 约束中添加或删除一个值;您只能使用一个新的集合重新定义这一列。如果您打算废弃一个选项,您可能会为历史数据而烦恼。作为一种策略,改变元数据——也就是说,改变表和列的定义——应该是不常见的,并且要注意测试和质量保证。有一个更好的解决方案来约束一列中的可选值:创建一张检查表,每一行包含一个允许在列中出现的候选值;然后在引用新表的旧表上声明一个外键约束。", Case:"create table tab1(status ENUM('new','in progress','fixed'))", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}
advisor.Rule{Item:"COL.011", Severity:"L0", Summary:"当需要唯一约束时才使用NULL,仅当列不能有缺失值时才使用NOT NULL", Content:"NULL和0是不同的,10乘以NULL还是NULL。NULL和空字符串是不一样的。将一个字符串和标准SQL中的NULL联合起来的结果还是NULL。NULL和FALSE也是不同的。AND、OR和NOT这三个布尔操作如果涉及NULL,其结果也让很多人感到困惑。当您将一列声明为NOT NULL时,也就是说这列中的每一个值都必须存在且是有意义的。使用NULL来表示任意类型不存在的空值。 当您将一列声明为NOT NULL时,也就是说这列中的每一个值都必须存在且是有意义的。", Case:"select c1,c2,c3 from tbl where c4 is null or c4 <> 1", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}} advisor.Rule{Item:"COL.011", Severity:"L0", Summary:"当需要唯一约束时才使用 NULL,仅当列不能有缺失值时才使用 NOT NULL", Content:"NULL 和0是不同的,10乘以 NULL 还是 NULL。NULL 和空字符串是不一样的。将一个字符串和标准 SQL 中的 NULL 联合起来的结果还是 NULL。NULL 和 FALSE 也是不同的。AND、OR 和 NOT 这三个布尔操作如果涉及 NULL,其结果也让很多人感到困惑。当您将一列声明为 NOT NULL 时,也就是说这列中的每一个值都必须存在且是有意义的。使用 NULL 来表示任意类型不存在的空值。 当您将一列声明为 NOT NULL 时,也就是说这列中的每一个值都必须存在且是有意义的。", Case:"select c1,c2,c3 from tbl where c4 is null or c4 <> 1", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}
advisor.Rule{Item:"COL.012", Severity:"L5", Summary:"BLOB和TEXT类型的字段不可设置为NULL", Content:"BLOB和TEXT类型的字段不可设置为NULL", Case:"CREATE TABLE `tbl` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `c` longblob, PRIMARY KEY (`id`));", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}} advisor.Rule{Item:"COL.012", Severity:"L5", Summary:"BLOB 和 TEXT 类型的字段不可设置为 NULL", Content:"BLOB 和 TEXT 类型的字段不可设置为 NULL", Case:"CREATE TABLE `tbl` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `c` longblob, PRIMARY KEY (`id`));", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}
advisor.Rule{Item:"COL.013", Severity:"L4", Summary:"TIMESTAMP类型未设置默认值", Content:"TIMESTAMP类型未设置默认值", Case:"CREATE TABLE tbl( `id` bigint not null, `create_time` timestamp);", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}} advisor.Rule{Item:"COL.013", Severity:"L4", Summary:"TIMESTAMP 类型未设置默认值", Content:"TIMESTAMP 类型未设置默认值", Case:"CREATE TABLE tbl( `id` bigint not null, `create_time` timestamp);", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}
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.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.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.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 xing, name from B) as e WHERE e.country_id = c.country_id", 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.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 {...}} 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 {...}}
advisor.Rule{Item:"FUN.001", Severity:"L2", Summary:"避免在 WHERE 条件中使用函数或其他运算符", Content:"虽然在 SQL 中使用函数可以简化很多复杂的查询,但使用了函数的查询无法利用表中已经建立的索引,该查询将会是全表扫描,性能较差。通常建议将列名写在比较运算符左侧,将查询过滤条件放在比较运算符右侧。也不建议在查询比较条件两侧书写多余的括号,这会对阅读产生比较大的困扰。", Case:"select id from t where substring(name,1,3)='abc'", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}} advisor.Rule{Item:"FUN.001", Severity:"L2", Summary:"避免在 WHERE 条件中使用函数或其他运算符", Content:"虽然在 SQL 中使用函数可以简化很多复杂的查询,但使用了函数的查询无法利用表中已经建立的索引,该查询将会是全表扫描,性能较差。通常建议将列名写在比较运算符左侧,将查询过滤条件放在比较运算符右侧。也不建议在查询比较条件两侧书写多余的括号,这会对阅读产生比较大的困扰。", Case:"select id from t where substring(name,1,3)='abc'", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}
advisor.Rule{Item:"FUN.002", Severity:"L1", Summary:"指定了 WHERE 条件或非 MyISAM 引擎时使用 COUNT(*) 操作性能不佳", 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", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}} advisor.Rule{Item:"FUN.002", Severity:"L1", Summary:"指定了 WHERE 条件或非 MyISAM 引擎时使用 COUNT(*) 操作性能不佳", 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", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}
advisor.Rule{Item:"FUN.003", Severity:"L3", Summary:"使用了合并为可空列的字符串连接", Content:"在一些查询请求中,您需要强制让某一列或者某个表达式返回非NULL的值,从而让查询逻辑变得更简单,担忧不想将这个值存下来。使用COALESCE()函数来构造连接的表达式,这样即使是空值列也不会使整表达式变为NULL。", Case:"select c1 || coalesce(' ' || c2 || ' ', ' ') || c3 as c from tbl", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}} advisor.Rule{Item:"FUN.003", Severity:"L3", Summary:"使用了合并为可空列的字符串连接", Content:"在一些查询请求中,您需要强制让某一列或者某个表达式返回非 NULL 的值,从而让查询逻辑变得更简单,担忧不想将这个值存下来。使用 COALESCE() 函数来构造连接的表达式,这样即使是空值列也不会使整表达式变为 NULL。", Case:"select c1 || coalesce(' ' || c2 || ' ', ' ') || c3 as c from tbl", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}
advisor.Rule{Item:"FUN.004", Severity:"L4", Summary:"不建议使用SYSDATE()函数", Content:"SYSDATE()函数可能导致主从数据不一致,请使用NOW()函数替代SYSDATE()。", Case:"SELECT SYSDATE();", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}} advisor.Rule{Item:"FUN.004", Severity:"L4", Summary:"不建议使用 SYSDATE() 函数", Content:"SYSDATE() 函数可能导致主从数据不一致,请使用 NOW() 函数替代 SYSDATE()。", Case:"SELECT SYSDATE();", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}
advisor.Rule{Item:"FUN.005", Severity:"L1", Summary:"不建议使用COUNT(col)或COUNT(常量)", Content:"不要使用COUNT(col)或COUNT(常量)来替代COUNT(*),COUNT(*)是SQL92定义的标准统计行数的方法,跟数据无关,跟NULL和非NULL也无关。", Case:"SELECT COUNT(1) FROM tbl;", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}} advisor.Rule{Item:"FUN.005", Severity:"L1", Summary:"不建议使用 COUNT(col) 或 COUNT(常量)", Content:"不要使用 COUNT(col) 或 COUNT(常量) 来替代 COUNT(*), COUNT(*) 是 SQL92 定义的标准统计行数的方法,跟数据无关,跟 NULL 和非 NULL 也无关。", Case:"SELECT COUNT(1) FROM tbl;", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}
advisor.Rule{Item:"FUN.006", Severity:"L1", Summary:"使用SUM(COL)时需注意NPE问题", Content:"当某一列的值全是NULL时,COUNT(COL)的返回结果为0,但SUM(COL)的返回结果为NULL,因此使用SUM()时需注意NPE问题。可以使用如下方式来避免SUM的NPE问题: SELECT IF(ISNULL(SUM(COL)), 0, SUM(COL)) FROM tbl", Case:"SELECT SUM(COL) FROM tbl;", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}} advisor.Rule{Item:"FUN.006", Severity:"L1", Summary:"使用 SUM(COL) 时需注意 NPE 问题", Content:"当某一列的值全是 NULL 时,COUNT(COL) 的返回结果为0,但 SUM(COL) 的返回结果为 NULL,因此使用 SUM() 时需注意 NPE 问题。可以使用如下方式来避免 SUM 的 NPE 问题: SELECT IF(ISNULL(SUM(COL)), 0, SUM(COL)) FROM tbl", Case:"SELECT SUM(COL) FROM tbl;", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}
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:"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.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.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.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.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 {...}} 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 {...}}
advisor.Rule{Item:"KEY.001", Severity:"L2", Summary:"建议使用自增列作为主键,如使用联合自增主键时请将自增键作为第一列", Content:"建议使用自增列作为主键,如使用联合自增主键时请将自增键作为第一列", Case:"create table test(`id` int(11) NOT NULL PRIMARY KEY (`id`))", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}} advisor.Rule{Item:"KEY.001", Severity:"L2", Summary:"建议使用自增列作为主键,如使用联合自增主键时请将自增键作为第一列", Content:"建议使用自增列作为主键,如使用联合自增主键时请将自增键作为第一列", Case:"create table test(`id` int(11) NOT NULL PRIMARY KEY (`id`))", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}
advisor.Rule{Item:"KEY.003", Severity:"L4", Summary:"避免外键等递归关系", Content:"存在递归关系的数据很常见,数据常会像树或者以层级方式组织。然而,创建一个外键约束来强制执行同一表中两列之间的关系,会导致笨拙的查询。树的每一层对应着另一个连接。您将需要发出递归查询,以获得节点的所有后代或所有祖先。解决方案是构造一个附加的闭包表。它记录了树中所有节点间的关系,而不仅仅是那些具有直接的父子关系。您也可以比较不同层次的数据设计:闭包表,路径枚举,嵌套集。然后根据应用程序的需要选择一个。", Case:"CREATE TABLE tab2 (p_id BIGINT UNSIGNED NOT NULL,a_id BIGINT UNSIGNED NOT NULL,PRIMARY KEY (p_id, a_id),FOREIGN KEY (p_id) REFERENCES tab1(p_id),FOREIGN KEY (a_id) REFERENCES tab3(a_id))", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}} advisor.Rule{Item:"KEY.003", Severity:"L4", Summary:"避免外键等递归关系", Content:"存在递归关系的数据很常见,数据常会像树或者以层级方式组织。然而,创建一个外键约束来强制执行同一表中两列之间的关系,会导致笨拙的查询。树的每一层对应着另一个连接。您将需要发出递归查询,以获得节点的所有后代或所有祖先。解决方案是构造一个附加的闭包表。它记录了树中所有节点间的关系,而不仅仅是那些具有直接的父子关系。您也可以比较不同层次的数据设计:闭包表,路径枚举,嵌套集。然后根据应用程序的需要选择一个。", Case:"CREATE TABLE tab2 (p_id BIGINT UNSIGNED NOT NULL,a_id BIGINT UNSIGNED NOT NULL,PRIMARY KEY (p_id, a_id),FOREIGN KEY (p_id) REFERENCES tab1(p_id),FOREIGN KEY (a_id) REFERENCES tab3(a_id))", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}
...@@ -70,17 +70,17 @@ advisor.Rule{Item:"KEY.004", Severity:"L0", Summary:"提醒:请将索引属性 ...@@ -70,17 +70,17 @@ advisor.Rule{Item:"KEY.004", Severity:"L0", Summary:"提醒:请将索引属性
advisor.Rule{Item:"KEY.005", Severity:"L2", Summary:"表建的索引过多", Content:"表建的索引过多", Case:"CREATE TABLE tbl ( a int, b int, c int, KEY idx_a (`a`),KEY idx_b(`b`),KEY idx_c(`c`));", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}} advisor.Rule{Item:"KEY.005", Severity:"L2", Summary:"表建的索引过多", Content:"表建的索引过多", Case:"CREATE TABLE tbl ( a int, b int, c int, KEY idx_a (`a`),KEY idx_b(`b`),KEY idx_c(`c`));", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}
advisor.Rule{Item:"KEY.006", Severity:"L4", Summary:"主键中的列过多", Content:"主键中的列过多", Case:"CREATE TABLE tbl ( a int, b int, c int, PRIMARY KEY(`a`,`b`,`c`));", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}} advisor.Rule{Item:"KEY.006", Severity:"L4", Summary:"主键中的列过多", Content:"主键中的列过多", Case:"CREATE TABLE tbl ( a int, b int, c int, PRIMARY KEY(`a`,`b`,`c`));", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}
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.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.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.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:"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.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.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 {...}} advisor.Rule{Item:"KWR.003", Severity:"L1", Summary:"不建议使用复数做列名或表名", Content:"表名应该仅仅表示表里面的实体内容,不应该表示实体数量,对应于 DO 类名也是单数形式,符合表达习惯。", Case:"CREATE TABLE tbl ( `books` int )", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}}
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.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:"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.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.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:"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:"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.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 {...}}
......
...@@ -2,11 +2,11 @@ ...@@ -2,11 +2,11 @@
[toc] [toc]
## 建议使用AS关键字显示声明一个别名 ## 建议使用 AS 关键字显示声明一个别名
* **Item**:ALI.001 * **Item**:ALI.001
* **Severity**:L0 * **Severity**:L0
* **Content**:在列或表别名(如"tbl AS alias")中, 明确使用AS关键字比隐含别名(如"tbl alias")更易懂。 * **Content**:在列或表别名(如"tbl AS alias")中, 明确使用 AS 关键字比隐含别名(如"tbl alias")更易懂。
* **Case**: * **Case**:
```sql ```sql
...@@ -16,7 +16,7 @@ select name from tbl t1 where id < 1000 ...@@ -16,7 +16,7 @@ select name from tbl t1 where id < 1000
* **Item**:ALI.002 * **Item**:ALI.002
* **Severity**:L8 * **Severity**:L8
* **Content**:例: "SELECT tbl.\* col1, col2"上面这条SQL给列通配符设置了别名,这样的SQL可能存在逻辑错误。您可能意在查询col1, 但是代替它的是重命名的是tbl的最后一列。 * **Content**:例: "SELECT tbl.\* col1, col2"上面这条 SQL 给列通配符设置了别名,这样的SQL可能存在逻辑错误。您可能意在查询 col1, 但是代替它的是重命名的是 tbl 的最后一列。
* **Case**: * **Case**:
```sql ```sql
...@@ -36,17 +36,17 @@ select name from tbl as tbl where id < 1000 ...@@ -36,17 +36,17 @@ select name from tbl as tbl where id < 1000
* **Item**:ALT.001 * **Item**:ALT.001
* **Severity**:L4 * **Severity**:L4
* **Content**:很多初学者会将ALTER TABLE tbl\_name [DEFAULT] CHARACTER SET 'UTF8'误认为会修改所有字段的字符集,但实际上它只会影响后续新增的字段不会改表已有字段的字符集。如果想修改整张表所有字段的字符集建议使用ALTER TABLE tbl\_name CONVERT TO CHARACTER SET charset\_name; * **Content**:很多初学者会将 ALTER TABLE tbl\_name [DEFAULT] CHARACTER SET 'UTF8' 误认为会修改所有字段的字符集,但实际上它只会影响后续新增的字段不会改表已有字段的字符集。如果想修改整张表所有字段的字符集建议使用 ALTER TABLE tbl\_name CONVERT TO CHARACTER SET charset\_name;
* **Case**: * **Case**:
```sql ```sql
ALTER TABLE tbl_name CONVERT TO CHARACTER SET charset_name; ALTER TABLE tbl_name CONVERT TO CHARACTER SET charset_name;
``` ```
## 同一张表的多条ALTER请求建议合为一条 ## 同一张表的多条 ALTER 请求建议合为一条
* **Item**:ALT.002 * **Item**:ALT.002
* **Severity**:L2 * **Severity**:L2
* **Content**:每次表结构变更对线上服务都会产生影响,即使是能够通过在线工具进行调整也请尽量通过合并ALTER请求的试减少操作次数。 * **Content**:每次表结构变更对线上服务都会产生影响,即使是能够通过在线工具进行调整也请尽量通过合并 ALTER 请求的试减少操作次数。
* **Case**: * **Case**:
```sql ```sql
...@@ -62,11 +62,11 @@ ALTER TABLE tbl ADD COLUMN col int, ADD INDEX idx_col (`col`); ...@@ -62,11 +62,11 @@ ALTER TABLE tbl ADD COLUMN col int, ADD INDEX idx_col (`col`);
```sql ```sql
ALTER TABLE tbl DROP COLUMN col; ALTER TABLE tbl DROP COLUMN col;
``` ```
## 删除主键和外键为高危操作,操作前请与DBA确认影响 ## 删除主键和外键为高危操作,操作前请与 DBA 确认影响
* **Item**:ALT.004 * **Item**:ALT.004
* **Severity**:L0 * **Severity**:L0
* **Content**:主键和外键为关系型数据库中两种重要约束,删除已有约束会打破已有业务逻辑,操作前请业务开发与DBA确认影响,三思而行。 * **Content**:主键和外键为关系型数据库中两种重要约束,删除已有约束会打破已有业务逻辑,操作前请业务开发与 DBA 确认影响,三思而行。
* **Case**: * **Case**:
```sql ```sql
...@@ -76,17 +76,17 @@ ALTER TABLE tbl DROP PRIMARY KEY; ...@@ -76,17 +76,17 @@ ALTER TABLE tbl DROP PRIMARY KEY;
* **Item**:ARG.001 * **Item**:ARG.001
* **Severity**:L4 * **Severity**:L4
* **Content**:例如“%foo”,查询参数有一个前项通配符的情况无法使用已有索引。 * **Content**:例如 "%foo",查询参数有一个前项通配符的情况无法使用已有索引。
* **Case**: * **Case**:
```sql ```sql
select c1,c2,c3 from tbl where name like '%foo' select c1,c2,c3 from tbl where name like '%foo'
``` ```
## 没有通配符的LIKE查询 ## 没有通配符的 LIKE 查询
* **Item**:ARG.002 * **Item**:ARG.002
* **Severity**:L1 * **Severity**:L1
* **Content**:不包含通配符的LIKE查询可能存在逻辑错误,因为逻辑上它与等值查询相同。 * **Content**:不包含通配符的 LIKE 查询可能存在逻辑错误,因为逻辑上它与等值查询相同。
* **Case**: * **Case**:
```sql ```sql
...@@ -102,31 +102,31 @@ select c1,c2,c3 from tbl where name like 'foo' ...@@ -102,31 +102,31 @@ select c1,c2,c3 from tbl where name like 'foo'
```sql ```sql
SELECT * FROM sakila.film WHERE length >= '60'; SELECT * FROM sakila.film WHERE length >= '60';
``` ```
## IN (NULL)/NOT IN (NULL)永远非真 ## IN (NULL)/NOT IN (NULL) 永远非真
* **Item**:ARG.004 * **Item**:ARG.004
* **Severity**:L4 * **Severity**:L4
* **Content**:正确的作法是col IN ('val1', 'val2', 'val3') OR col IS NULL * **Content**:正确的作法是 col IN ('val1', 'val2', 'val3') OR col IS NULL
* **Case**: * **Case**:
```sql ```sql
SELECT * FROM tb WHERE col IN (NULL); SELECT * FROM tb WHERE col IN (NULL);
``` ```
## IN要慎用,元素过多会导致全表扫描 ## IN 要慎用,元素过多会导致全表扫描
* **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
select id from t where num in(1,2,3) select id from t where num in(1,2,3)
``` ```
## 应尽量避免在WHERE子句中对字段进行NULL值判断 ## 应尽量避免在 WHERE 子句中对字段进行 NULL 值判断
* **Item**:ARG.006 * **Item**:ARG.006
* **Severity**:L1 * **Severity**:L1
* **Content**:使用IS NULL或IS NOT NULL将可能导致引擎放弃使用索引而进行全表扫描,如:select id from t where num is null;可以在num上设置默认值0,确保表中num列没有null值,然后这样查询: select id from t where num=0; * **Content**:使用 IS NULL 或 IS NOT NULL 将可能导致引擎放弃使用索引而进行全表扫描,如:select id from t where num is null;可以在num上设置默认值0,确保表中 num 列没有 NULL 值,然后这样查询: select id from t where num=0;
* **Case**: * **Case**:
```sql ```sql
...@@ -136,7 +136,7 @@ select id from t where num is null ...@@ -136,7 +136,7 @@ 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
...@@ -146,7 +146,7 @@ select c_id,c2,c3 from tbl where c2 like 'test%' ...@@ -146,7 +146,7 @@ select c_id,c2,c3 from tbl where c2 like 'test%'
* **Item**:ARG.008 * **Item**:ARG.008
* **Severity**:L1 * **Severity**:L1
* **Content**:IN-list谓词可以用于索引检索,并且优化器可以对IN-list进行排序,以匹配索引的排序序列,从而获得更有效的检索。请注意,IN-list必须只包含常量,或在查询块执行期间保持常量的值,例如外引用。 * **Content**:IN-list 谓词可以用于索引检索,并且优化器可以对 IN-list 进行排序,以匹配索引的排序序列,从而获得更有效的检索。请注意,IN-list 必须只包含常量,或在查询块执行期间保持常量的值,例如外引用。
* **Case**: * **Case**:
```sql ```sql
...@@ -156,13 +156,13 @@ SELECT c1,c2,c3 FROM tbl WHERE c1 = 14 OR c1 = 17 ...@@ -156,13 +156,13 @@ 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
SELECT 'abc ' SELECT 'abc '
``` ```
## 不要使用hint,如sql\_no\_cache, force index, ignore key, straight join等 ## 不要使用 hint,如:sql\_no\_cache, force index, ignore key, straight join等
* **Item**:ARG.010 * **Item**:ARG.010
* **Severity**:L1 * **Severity**:L1
...@@ -182,101 +182,101 @@ SELECT * FROM t1 USE INDEX (i1) ORDER BY a; ...@@ -182,101 +182,101 @@ SELECT * FROM t1 USE INDEX (i1) ORDER BY a;
```sql ```sql
select id from t where num not in(1,2,3); select id from t where num not in(1,2,3);
``` ```
## 最外层SELECT未指定WHERE条件 ## 最外层 SELECT 未指定 WHERE 条件
* **Item**:CLA.001 * **Item**:CLA.001
* **Severity**:L4 * **Severity**:L4
* **Content**:SELECT语句没有WHERE子句,可能检查比预期更多的行(全表扫描)。对于SELECT COUNT(\*)类型的请求如果不要求精度,建议使用SHOW TABLE STATUS或EXPLAIN替代。 * **Content**:SELECT 语句没有 WHERE 子句,可能检查比预期更多的行(全表扫描)。对于 SELECT COUNT(\*) 类型的请求如果不要求精度,建议使用 SHOW TABLE STATUS 或 EXPLAIN 替代。
* **Case**: * **Case**:
```sql ```sql
select id from tbl select id from tbl
``` ```
## 不建议使用ORDER BY RAND() ## 不建议使用 ORDER BY RAND()
* **Item**:CLA.002 * **Item**:CLA.002
* **Severity**:L3 * **Severity**:L3
* **Content**:ORDER BY RAND()是从结果集中检索随机行的一种非常低效的方法,因为它会对整个结果进行排序并丢弃其大部分数据。 * **Content**:ORDER BY RAND() 是从结果集中检索随机行的一种非常低效的方法,因为它会对整个结果进行排序并丢弃其大部分数据。
* **Case**: * **Case**:
```sql ```sql
select name from tbl where id < 1000 order by rand(number) select name from tbl where id < 1000 order by rand(number)
``` ```
## 不建议使用带OFFSET的LIMIT查询 ## 不建议使用带 OFFSET 的LIMIT 查询
* **Item**:CLA.003 * **Item**:CLA.003
* **Severity**:L2 * **Severity**:L2
* **Content**:使用LIMIT和OFFSET对结果集分页的复杂度是O(n^2),并且会随着数据增大而导致性能问题。采用“书签”扫描的方法实现分页效率更高。 * **Content**:使用 LIMIT 和 OFFSET 对结果集分页的复杂度是 O(n^2),并且会随着数据增大而导致性能问题。采用“书签”扫描的方法实现分页效率更高。
* **Case**: * **Case**:
```sql ```sql
select c1,c2 from tbl where name=xx order by number limit 1 offset 20 select c1,c2 from tbl where name=xx order by number limit 1 offset 20
``` ```
## 不建议对常量进行GROUP BY ## 不建议对常量进行 GROUP BY
* **Item**:CLA.004 * **Item**:CLA.004
* **Severity**:L2 * **Severity**:L2
* **Content**:GROUP BY 1 表示按第一列进行GROUP BY。如果在GROUP BY子句中使用数字,而不是表达式或列名称,当查询列顺序改变时,可能会导致问题。 * **Content**:GROUP BY 1 表示按第一列进行 GROUP BY。如果在 GROUP BY 子句中使用数字,而不是表达式或列名称,当查询列顺序改变时,可能会导致问题。
* **Case**: * **Case**:
```sql ```sql
select col1,col2 from tbl group by 1 select col1,col2 from tbl group by 1
``` ```
## ORDER BY常数列没有任何意义 ## ORDER BY 常数列没有任何意义
* **Item**:CLA.005 * **Item**:CLA.005
* **Severity**:L2 * **Severity**:L2
* **Content**:SQL逻辑上可能存在错误; 最多只是一个无用的操作,不会更改查询结果。 * **Content**:SQL 逻辑上可能存在错误; 最多只是一个无用的操作,不会更改查询结果。
* **Case**: * **Case**:
```sql ```sql
select id from test where id=1 order by id select id from test where id=1 order by id
``` ```
## 在不同的表中GROUP BY或ORDER BY ## 在不同的表中 GROUP BY 或 ORDER BY
* **Item**:CLA.006 * **Item**:CLA.006
* **Severity**:L4 * **Severity**:L4
* **Content**:这将强制使用临时表和filesort,可能产生巨大性能隐患,并且可能消耗大量内存和磁盘上的临时空间。 * **Content**:这将强制使用临时表和 filesort,可能产生巨大性能隐患,并且可能消耗大量内存和磁盘上的临时空间。
* **Case**: * **Case**:
```sql ```sql
select tb1.col, tb2.col from tb1, tb2 where id=1 group by tb1.col, tb2.col select tb1.col, tb2.col from tb1, tb2 where id=1 group by tb1.col, tb2.col
``` ```
## ORDER BY语句对多个不同条件使用不同方向的排序无法使用索引 ## ORDER BY 语句对多个不同条件使用不同方向的排序无法使用索引
* **Item**:CLA.007 * **Item**:CLA.007
* **Severity**:L2 * **Severity**:L2
* **Content**:ORDER BY子句中的所有表达式必须按统一的ASC或DESC方向排序,以便利用索引。 * **Content**:ORDER BY 子句中的所有表达式必须按统一的 ASC 或 DESC 方向排序,以便利用索引。
* **Case**: * **Case**:
```sql ```sql
select c1,c2,c3 from t1 where c1='foo' order by c2 desc, c3 asc select c1,c2,c3 from t1 where c1='foo' order by c2 desc, c3 asc
``` ```
## 请为GROUP BY显示添加ORDER BY条件 ## 请为 GROUP BY 显示添加 ORDER BY 条件
* **Item**:CLA.008 * **Item**:CLA.008
* **Severity**:L2 * **Severity**:L2
* **Content**:默认MySQL会对'GROUP BY col1, col2, ...'请求按如下顺序排序'ORDER BY col1, col2, ...'。如果GROUP BY语句不指定ORDER BY条件会导致无谓的排序产生,如果不需要排序建议添加'ORDER BY NULL'。 * **Content**:默认 MySQL 会对 'GROUP BY col1, col2, ...' 请求按如下顺序排序 'ORDER BY col1, col2, ...'。如果 GROUP BY 语句不指定 ORDER BY 条件会导致无谓的排序产生,如果不需要排序建议添加 'ORDER BY NULL'。
* **Case**: * **Case**:
```sql ```sql
select c1,c2,c3 from t1 where c1='foo' group by c2 select c1,c2,c3 from t1 where c1='foo' group by c2
``` ```
## ORDER BY的条件为表达式 ## ORDER BY 的条件为表达式
* **Item**:CLA.009 * **Item**:CLA.009
* **Severity**:L2 * **Severity**:L2
* **Content**:当ORDER BY条件为表达式或函数时会使用到临时表,如果在未指定WHERE或WHERE条件返回的结果集较大时性能会很差。 * **Content**:当 ORDER BY 条件为表达式或函数时会使用到临时表,如果在未指定 WHERE 或 WHERE 条件返回的结果集较大时性能会很差。
* **Case**: * **Case**:
```sql ```sql
select description from film where title ='ACADEMY DINOSAUR' order by length-language_id; select description from film where title ='ACADEMY DINOSAUR' order by length-language_id;
``` ```
## GROUP BY的条件为表达式 ## GROUP BY 的条件为表达式
* **Item**:CLA.010 * **Item**:CLA.010
* **Severity**:L2 * **Severity**:L2
* **Content**:当GROUP BY条件为表达式或函数时会使用到临时表,如果在未指定WHERE或WHERE条件返回的结果集较大时性能会很差。 * **Content**:当 GROUP BY 条件为表达式或函数时会使用到临时表,如果在未指定 WHERE 或 WHERE 条件返回的结果集较大时性能会很差。
* **Case**: * **Case**:
```sql ```sql
...@@ -300,39 +300,39 @@ CREATE TABLE `test1` (`ID` bigint(20) NOT NULL AUTO_INCREMENT,`c1` varchar(128) ...@@ -300,39 +300,39 @@ CREATE TABLE `test1` (`ID` bigint(20) NOT NULL AUTO_INCREMENT,`c1` varchar(128)
* **Case**: * **Case**:
```sql ```sql
这是一条很长很长的SQL,案例略。 这是一条很长很长的 SQL,案例略。
``` ```
## 不建议使用HAVING子句 ## 不建议使用 HAVING 子句
* **Item**:CLA.013 * **Item**:CLA.013
* **Severity**:L3 * **Severity**:L3
* **Content**:将查询的HAVING子句改写为WHERE中的查询条件,可以在查询处理期间使用索引。 * **Content**:将查询的 HAVING 子句改写为 WHERE 中的查询条件,可以在查询处理期间使用索引。
* **Case**: * **Case**:
```sql ```sql
SELECT s.c_id,count(s.c_id) FROM s where c = test GROUP BY s.c_id HAVING s.c_id <> '1660' AND s.c_id <> '2' order by s.c_id SELECT s.c_id,count(s.c_id) FROM s where c = test GROUP BY s.c_id HAVING s.c_id <> '1660' AND s.c_id <> '2' order by s.c_id
``` ```
## 删除全表时建议使用TRUNCATE替代DELETE ## 删除全表时建议使用 TRUNCATE 替代 DELETE
* **Item**:CLA.014 * **Item**:CLA.014
* **Severity**:L2 * **Severity**:L2
* **Content**:删除全表时建议使用TRUNCATE替代DELETE * **Content**:删除全表时建议使用 TRUNCATE 替代 DELETE
* **Case**: * **Case**:
```sql ```sql
delete from tbl delete from tbl
``` ```
## UPDATE未指定WHERE条件 ## UPDATE 未指定 WHERE 条件
* **Item**:CLA.015 * **Item**:CLA.015
* **Severity**:L4 * **Severity**:L4
* **Content**:UPDATE不指定WHERE条件一般是致命的,请您三思后行 * **Content**:UPDATE 不指定 WHERE 条件一般是致命的,请您三思后行
* **Case**: * **Case**:
```sql ```sql
update tbl set col=1 update tbl set col=1
``` ```
## 不要UPDATE主键 ## 不要 UPDATE 主键
* **Item**:CLA.016 * **Item**:CLA.016
* **Severity**:L2 * **Severity**:L2
...@@ -346,37 +346,37 @@ update tbl set col=1 ...@@ -346,37 +346,37 @@ update tbl set col=1
* **Item**:CLA.017 * **Item**:CLA.017
* **Severity**:L2 * **Severity**:L2
* **Content**:这些功能的使用在一定程度上会使得程序难以调试和拓展,更没有移植性,且会极大的增加出现BUG的概率。 * **Content**:这些功能的使用在一定程度上会使得程序难以调试和拓展,更没有移植性,且会极大的增加出现 BUG 的概率。
* **Case**: * **Case**:
```sql ```sql
CREATE VIEW v_today (today) AS SELECT CURRENT_DATE; CREATE VIEW v_today (today) AS SELECT CURRENT_DATE;
``` ```
## 不建议使用SELECT \* 类型查询 ## 不建议使用 SELECT \* 类型查询
* **Item**:COL.001 * **Item**:COL.001
* **Severity**:L1 * **Severity**:L1
* **Content**:当表结构变更时,使用\*通配符选择所有列将导致查询的含义和行为会发生更改,可能导致查询返回更多的数据。 * **Content**:当表结构变更时,使用 \* 通配符选择所有列将导致查询的含义和行为会发生更改,可能导致查询返回更多的数据。
* **Case**: * **Case**:
```sql ```sql
select * from tbl where id=1 select * from tbl where id=1
``` ```
## INSERT未指定列名 ## INSERT/REPLACE 未指定列名
* **Item**:COL.002 * **Item**:COL.002
* **Severity**:L2 * **Severity**:L2
* **Content**:当表结构发生变更,如果INSERT或REPLACE请求不明确指定列名,请求的结果将会与预想的不同; 建议使用“INSERT INTO tbl(col1,col2)VALUES ...”代替。 * **Content**:当表结构发生变更,如果 INSERT 或 REPLACE 请求不明确指定列名,请求的结果将会与预想的不同; 建议使用 “INSERT INTO tbl(col1,col2)VALUES ...” 代替。
* **Case**: * **Case**:
```sql ```sql
insert into tbl values(1,'name') insert into tbl values(1,'name')
``` ```
## 建议修改自增ID为无符号类型 ## 建议修改自增 ID 为无符号类型
* **Item**:COL.003 * **Item**:COL.003
* **Severity**:L2 * **Severity**:L2
* **Content**:建议修改自增ID为无符号类型 * **Content**:建议修改自增 ID 为无符号类型
* **Case**: * **Case**:
```sql ```sql
...@@ -386,7 +386,7 @@ create table test(`id` int(11) NOT NULL AUTO_INCREMENT) ...@@ -386,7 +386,7 @@ create table test(`id` int(11) NOT NULL AUTO_INCREMENT)
* **Item**:COL.004 * **Item**:COL.004
* **Severity**:L1 * **Severity**:L1
* **Content**:请为列添加默认值,如果是ALTER操作,请不要忘记将原字段的默认值写上。字段无默认值,当表较大时无法在线变更表结构。 * **Content**:请为列添加默认值,如果是 ALTER 操作,请不要忘记将原字段的默认值写上。字段无默认值,当表较大时无法在线变更表结构。
* **Case**: * **Case**:
```sql ```sql
...@@ -412,7 +412,7 @@ CREATE TABLE tbl (col int) ENGINE=InnoDB; ...@@ -412,7 +412,7 @@ CREATE TABLE tbl (col int) ENGINE=InnoDB;
```sql ```sql
CREATE TABLE tbl ( cols ....); CREATE TABLE tbl ( cols ....);
``` ```
## 可使用VARCHAR代替CHAR,VARBINARY代替BINARY ## 可使用 VARCHAR 代替 CHAR, VARBINARY 代替 BINARY
* **Item**:COL.008 * **Item**:COL.008
* **Severity**:L1 * **Severity**:L1
...@@ -426,47 +426,47 @@ create table t1(id int,name char(20),last_time date) ...@@ -426,47 +426,47 @@ create table t1(id int,name char(20),last_time date)
* **Item**:COL.009 * **Item**:COL.009
* **Severity**:L2 * **Severity**:L2
* **Content**:实际上,任何使用FLOAT、REAL或DOUBLE PRECISION数据类型的设计都有可能是反模式。大多数应用程序使用的浮点数的取值范围并不需要达到IEEE 754标准所定义的最大/最小区间。在计算总量时,非精确浮点数所积累的影响是严重的。使用SQL中的NUMERIC或DECIMAL类型来代替FLOAT及其类似的数据类型进行固定精度的小数存储。这些数据类型精确地根据您定义这一列时指定的精度来存储数据。尽可能不要使用浮点数。 * **Content**:实际上,任何使用 FLOAT, REAL 或 DOUBLE PRECISION 数据类型的设计都有可能是反模式。大多数应用程序使用的浮点数的取值范围并不需要达到IEEE 754标准所定义的最大/最小区间。在计算总量时,非精确浮点数所积累的影响是严重的。使用 SQL 中的 NUMERIC 或 DECIMAL 类型来代替 FLOAT 及其类似的数据类型进行固定精度的小数存储。这些数据类型精确地根据您定义这一列时指定的精度来存储数据。尽可能不要使用浮点数。
* **Case**: * **Case**:
```sql ```sql
CREATE TABLE tab2 (p_id BIGINT UNSIGNED NOT NULL,a_id BIGINT UNSIGNED NOT NULL,hours float not null,PRIMARY KEY (p_id, a_id)) CREATE TABLE tab2 (p_id BIGINT UNSIGNED NOT NULL,a_id BIGINT UNSIGNED NOT NULL,hours float not null,PRIMARY KEY (p_id, a_id))
``` ```
## 不建议使用ENUM数据类型 ## 不建议使用 ENUM 数据类型
* **Item**:COL.010 * **Item**:COL.010
* **Severity**:L2 * **Severity**:L2
* **Content**:ENUM定义了列中值的类型,使用字符串表示ENUM里的值时,实际存储在列中的数据是这些值在定义时的序数。因此,这列的数据是字节对齐的,当您进行一次排序查询时,结果是按照实际存储的序数值排序的,而不是按字符串值的字母顺序排序的。这可能不是您所希望的。没有什么语法支持从ENUM或者check约束中添加或删除一个值;您只能使用一个新的集合重新定义这一列。如果您打算废弃一个选项,您可能会为历史数据而烦恼。作为一种策略,改变元数据——也就是说,改变表和列的定义——应该是不常见的,并且要注意测试和质量保证。有一个更好的解决方案来约束一列中的可选值:创建一张检查表,每一行包含一个允许在列中出现的候选值;然后在引用新表的旧表上声明一个外键约束。 * **Content**:ENUM 定义了列中值的类型,使用字符串表示 ENUM 里的值时,实际存储在列中的数据是这些值在定义时的序数。因此,这列的数据是字节对齐的,当您进行一次排序查询时,结果是按照实际存储的序数值排序的,而不是按字符串值的字母顺序排序的。这可能不是您所希望的。没有什么语法支持从 ENUM 或者 check 约束中添加或删除一个值;您只能使用一个新的集合重新定义这一列。如果您打算废弃一个选项,您可能会为历史数据而烦恼。作为一种策略,改变元数据——也就是说,改变表和列的定义——应该是不常见的,并且要注意测试和质量保证。有一个更好的解决方案来约束一列中的可选值:创建一张检查表,每一行包含一个允许在列中出现的候选值;然后在引用新表的旧表上声明一个外键约束。
* **Case**: * **Case**:
```sql ```sql
create table tab1(status ENUM('new','in progress','fixed')) create table tab1(status ENUM('new','in progress','fixed'))
``` ```
## 当需要唯一约束时才使用NULL,仅当列不能有缺失值时才使用NOT NULL ## 当需要唯一约束时才使用 NULL,仅当列不能有缺失值时才使用 NOT NULL
* **Item**:COL.011 * **Item**:COL.011
* **Severity**:L0 * **Severity**:L0
* **Content**:NULL和0是不同的,10乘以NULL还是NULL。NULL和空字符串是不一样的。将一个字符串和标准SQL中的NULL联合起来的结果还是NULL。NULL和FALSE也是不同的。AND、OR和NOT这三个布尔操作如果涉及NULL,其结果也让很多人感到困惑。当您将一列声明为NOT NULL时,也就是说这列中的每一个值都必须存在且是有意义的。使用NULL来表示任意类型不存在的空值。 当您将一列声明为NOT NULL时,也就是说这列中的每一个值都必须存在且是有意义的。 * **Content**:NULL 和0是不同的,10乘以 NULL 还是 NULL。NULL 和空字符串是不一样的。将一个字符串和标准 SQL 中的 NULL 联合起来的结果还是 NULL。NULL 和 FALSE 也是不同的。AND、OR 和 NOT 这三个布尔操作如果涉及 NULL,其结果也让很多人感到困惑。当您将一列声明为 NOT NULL 时,也就是说这列中的每一个值都必须存在且是有意义的。使用 NULL 来表示任意类型不存在的空值。 当您将一列声明为 NOT NULL 时,也就是说这列中的每一个值都必须存在且是有意义的。
* **Case**: * **Case**:
```sql ```sql
select c1,c2,c3 from tbl where c4 is null or c4 <> 1 select c1,c2,c3 from tbl where c4 is null or c4 <> 1
``` ```
## BLOB和TEXT类型的字段不可设置为NULL ## BLOB 和 TEXT 类型的字段不可设置为 NULL
* **Item**:COL.012 * **Item**:COL.012
* **Severity**:L5 * **Severity**:L5
* **Content**:BLOB和TEXT类型的字段不可设置为NULL * **Content**:BLOB 和 TEXT 类型的字段不可设置为 NULL
* **Case**: * **Case**:
```sql ```sql
CREATE TABLE `tbl` ( `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`));
``` ```
## TIMESTAMP类型未设置默认值 ## TIMESTAMP 类型未设置默认值
* **Item**:COL.013 * **Item**:COL.013
* **Severity**:L4 * **Severity**:L4
* **Content**:TIMESTAMP类型未设置默认值 * **Content**:TIMESTAMP 类型未设置默认值
* **Case**: * **Case**:
```sql ```sql
...@@ -482,17 +482,17 @@ CREATE TABLE tbl( `id` bigint not null, `create_time` timestamp); ...@@ -482,17 +482,17 @@ CREATE TABLE tbl( `id` bigint not null, `create_time` timestamp);
```sql ```sql
CREATE TABLE `tb2` ( `id` int(11) DEFAULT NULL, `col` char(10) CHARACTER SET utf8 DEFAULT NULL) CREATE TABLE `tb2` ( `id` int(11) DEFAULT NULL, `col` char(10) CHARACTER SET utf8 DEFAULT NULL)
``` ```
## BLOB类型的字段不可指定默认值 ## BLOB 类型的字段不可指定默认值
* **Item**:COL.015 * **Item**:COL.015
* **Severity**:L4 * **Severity**:L4
* **Content**:BLOB类型的字段不可指定默认值 * **Content**:BLOB 类型的字段不可指定默认值
* **Case**: * **Case**:
```sql ```sql
CREATE TABLE `tbl` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `c` blob NOT NULL DEFAULT '', PRIMARY KEY (`id`)); CREATE TABLE `tbl` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `c` blob NOT NULL DEFAULT '', PRIMARY KEY (`id`));
``` ```
## 整型定义建议采用INT(10)或BIGINT(20) ## 整型定义建议采用 INT(10) 或 BIGINT(20)
* **Item**:COL.016 * **Item**:COL.016
* **Severity**:L1 * **Severity**:L1
...@@ -502,7 +502,7 @@ CREATE TABLE `tbl` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `c` blob NOT ...@@ -502,7 +502,7 @@ CREATE TABLE `tbl` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `c` blob NOT
```sql ```sql
CREATE TABLE tab (a INT(1)); CREATE TABLE tab (a INT(1));
``` ```
## varchar定义长度过长 ## VARCHAR 定义长度过长
* **Item**:COL.017 * **Item**:COL.017
* **Severity**:L2 * **Severity**:L2
...@@ -512,7 +512,7 @@ CREATE TABLE tab (a INT(1)); ...@@ -512,7 +512,7 @@ CREATE TABLE tab (a INT(1));
```sql ```sql
CREATE TABLE tab (a varchar(3500)); CREATE TABLE tab (a varchar(3500));
``` ```
## 消除不必要的DISTINCT条件 ## 消除不必要的 DISTINCT 条件
* **Item**:DIS.001 * **Item**:DIS.001
* **Severity**:L1 * **Severity**:L1
...@@ -520,19 +520,19 @@ CREATE TABLE tab (a varchar(3500)); ...@@ -520,19 +520,19 @@ CREATE TABLE tab (a varchar(3500));
* **Case**: * **Case**:
```sql ```sql
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 xing, name from B) as e WHERE e.country_id = c.country_id 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
``` ```
## COUNT(DISTINCT)多列时结果可能和你预想的不同 ## COUNT(DISTINCT) 多列时结果可能和你预想的不同
* **Item**:DIS.002 * **Item**:DIS.002
* **Severity**:L3 * **Severity**:L3
* **Content**:COUNT(DISTINCT col)计算该列除NULL之外的不重复行数,注意COUNT(DISTINCT col, col2)如果其中一列全为NULL那么即使另一列有不同的值,也返回0。 * **Content**:COUNT(DISTINCT col) 计算该列除NULL之外的不重复行数,注意 COUNT(DISTINCT col, col2) 如果其中一列全为 NULL 那么即使另一列有不同的值,也返回0。
* **Case**: * **Case**:
```sql ```sql
SELECT COUNT(DISTINCT col, col2) FROM tbl; SELECT COUNT(DISTINCT col, col2) FROM tbl;
``` ```
## DISTINCT \*对有主键的表没有意义 ## DISTINCT \* 对有主键的表没有意义
* **Item**:DIS.003 * **Item**:DIS.003
* **Severity**:L3 * **Severity**:L3
...@@ -556,7 +556,7 @@ select id from t where substring(name,1,3)='abc' ...@@ -556,7 +556,7 @@ select id from t where substring(name,1,3)='abc'
* **Item**:FUN.002 * **Item**:FUN.002
* **Severity**:L1 * **Severity**:L1
* **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**: * **Case**:
```sql ```sql
...@@ -566,47 +566,47 @@ SELECT c3, COUNT(*) AS accounts FROM tab where c2 < 10000 GROUP BY c3 ORDER BY n ...@@ -566,47 +566,47 @@ SELECT c3, COUNT(*) AS accounts FROM tab where c2 < 10000 GROUP BY c3 ORDER BY n
* **Item**:FUN.003 * **Item**:FUN.003
* **Severity**:L3 * **Severity**:L3
* **Content**:在一些查询请求中,您需要强制让某一列或者某个表达式返回非NULL的值,从而让查询逻辑变得更简单,担忧不想将这个值存下来。使用COALESCE()函数来构造连接的表达式,这样即使是空值列也不会使整表达式变为NULL。 * **Content**:在一些查询请求中,您需要强制让某一列或者某个表达式返回非 NULL 的值,从而让查询逻辑变得更简单,担忧不想将这个值存下来。使用 COALESCE() 函数来构造连接的表达式,这样即使是空值列也不会使整表达式变为 NULL。
* **Case**: * **Case**:
```sql ```sql
select c1 || coalesce(' ' || c2 || ' ', ' ') || c3 as c from tbl select c1 || coalesce(' ' || c2 || ' ', ' ') || c3 as c from tbl
``` ```
## 不建议使用SYSDATE()函数 ## 不建议使用 SYSDATE() 函数
* **Item**:FUN.004 * **Item**:FUN.004
* **Severity**:L4 * **Severity**:L4
* **Content**:SYSDATE()函数可能导致主从数据不一致,请使用NOW()函数替代SYSDATE()。 * **Content**:SYSDATE() 函数可能导致主从数据不一致,请使用 NOW() 函数替代 SYSDATE()。
* **Case**: * **Case**:
```sql ```sql
SELECT SYSDATE(); SELECT SYSDATE();
``` ```
## 不建议使用COUNT(col)或COUNT(常量) ## 不建议使用 COUNT(col) 或 COUNT(常量)
* **Item**:FUN.005 * **Item**:FUN.005
* **Severity**:L1 * **Severity**:L1
* **Content**:不要使用COUNT(col)或COUNT(常量)来替代COUNT(\*),COUNT(\*)是SQL92定义的标准统计行数的方法,跟数据无关,跟NULL和非NULL也无关。 * **Content**:不要使用 COUNT(col) 或 COUNT(常量) 来替代 COUNT(\*), COUNT(\*) 是 SQL92 定义的标准统计行数的方法,跟数据无关,跟 NULL 和非 NULL 也无关。
* **Case**: * **Case**:
```sql ```sql
SELECT COUNT(1) FROM tbl; SELECT COUNT(1) FROM tbl;
``` ```
## 使用SUM(COL)时需注意NPE问题 ## 使用 SUM(COL) 时需注意 NPE 问题
* **Item**:FUN.006 * **Item**:FUN.006
* **Severity**:L1 * **Severity**:L1
* **Content**:当某一列的值全是NULL时,COUNT(COL)的返回结果为0,但SUM(COL)的返回结果为NULL,因此使用SUM()时需注意NPE问题。可以使用如下方式来避免SUM的NPE问题: SELECT IF(ISNULL(SUM(COL)), 0, SUM(COL)) FROM tbl * **Content**:当某一列的值全是 NULL 时,COUNT(COL) 的返回结果为0,但 SUM(COL) 的返回结果为 NULL,因此使用 SUM() 时需注意 NPE 问题。可以使用如下方式来避免 SUM 的 NPE 问题: SELECT IF(ISNULL(SUM(COL)), 0, SUM(COL)) FROM tbl
* **Case**: * **Case**:
```sql ```sql
SELECT SUM(COL) FROM tbl; SELECT SUM(COL) FROM tbl;
``` ```
## 不建议对等值查询列使用GROUP BY ## 不建议对等值查询列使用 GROUP BY
* **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
...@@ -616,7 +616,7 @@ select film_id, title from film where release_year='2006' group by release_year ...@@ -616,7 +616,7 @@ select film_id, title from film where release_year='2006' group by release_year
* **Item**:JOI.001 * **Item**:JOI.001
* **Severity**:L2 * **Severity**:L2
* **Content**:表连接的时候混用逗号和ANSI JOIN不便于人类理解,并且MySQL不同版本的表连接行为和优先级均有所不同,当MySQL版本变化后可能会引入错误。 * **Content**:表连接的时候混用逗号和 ANSI JOIN 不便于人类理解,并且MySQL不同版本的表连接行为和优先级均有所不同,当 MySQL 版本变化后可能会引入错误。
* **Case**: * **Case**:
```sql ```sql
...@@ -626,43 +626,43 @@ select c1,c2,c3 from t1,t2 join t3 on t1.c1=t2.c1,t1.c3=t3,c1 where id>1000 ...@@ -626,43 +626,43 @@ select c1,c2,c3 from t1,t2 join t3 on t1.c1=t2.c1,t1.c3=t3,c1 where id>1000
* **Item**:JOI.002 * **Item**:JOI.002
* **Severity**:L4 * **Severity**:L4
* **Content**:相同的表在FROM子句中至少出现两次,可以简化为对该表的单次访问。 * **Content**:相同的表在 FROM 子句中至少出现两次,可以简化为对该表的单次访问。
* **Case**: * **Case**:
```sql ```sql
select tb1.col from (tb1, tb2) join tb2 on tb1.id=tb.id where tb1.id=1 select tb1.col from (tb1, tb2) join tb2 on tb1.id=tb.id where tb1.id=1
``` ```
## OUTER JOIN失效 ## OUTER JOIN 失效
* **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
select c1,c2,c3 from t1 left outer join t2 using(c1) where t1.c2=2 and t2.c3=4 select c1,c2,c3 from t1 left outer join t2 using(c1) where t1.c2=2 and t2.c3=4
``` ```
## 不建议使用排它JOIN ## 不建议使用排它 JOIN
* **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
select c1,c2,c3 from t1 left outer join t2 on t1.c1=t2.c1 where t2.c2 is null select c1,c2,c3 from t1 left outer join t2 on t1.c1=t2.c1 where t2.c2 is null
``` ```
## 减少JOIN的数量 ## 减少 JOIN 的数量
* **Item**:JOI.005 * **Item**:JOI.005
* **Severity**:L2 * **Severity**:L2
* **Content**:太多的JOIN是复杂的裹脚布式查询的症状。考虑将复杂查询分解成许多简单的查询,并减少JOIN的数量。 * **Content**:太多的 JOIN 是复杂的裹脚布式查询的症状。考虑将复杂查询分解成许多简单的查询,并减少 JOIN 的数量。
* **Case**: * **Case**:
```sql ```sql
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 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
``` ```
## 将嵌套查询重写为JOIN通常会导致更高效的执行和更有效的优化 ## 将嵌套查询重写为 JOIN 通常会导致更高效的执行和更有效的优化
* **Item**:JOI.006 * **Item**:JOI.006
* **Severity**:L4 * **Severity**:L4
...@@ -676,7 +676,7 @@ SELECT s,p,d FROM tbl WHERE p.p_id = (SELECT s.p_id FROM tbl WHERE s.c_id = 1009 ...@@ -676,7 +676,7 @@ SELECT s,p,d FROM tbl WHERE p.p_id = (SELECT s.p_id FROM tbl WHERE s.c_id = 1009
* **Item**:JOI.007 * **Item**:JOI.007
* **Severity**:L4 * **Severity**:L4
* **Content**:当需要同时更新多张表时建议使用简单SQL,一条SQL只更新一张表,尽量不要将多张表的更新在同一条SQL中完成。 * **Content**:当需要同时更新多张表时建议使用简单 SQL,一条 SQL 只更新一张表,尽量不要将多张表的更新在同一条 SQL 中完成。
* **Case**: * **Case**:
```sql ```sql
...@@ -762,11 +762,11 @@ CREATE TABLE tbl ( a int, b int, c int, PRIMARY KEY(`a`,`b`,`c`)); ...@@ -762,11 +762,11 @@ CREATE TABLE tbl ( a int, b int, c int, PRIMARY KEY(`a`,`b`,`c`));
```sql ```sql
CREATE TABLE tbl (a int); CREATE TABLE tbl (a int);
``` ```
## ORDER BY多个列但排序方向不同时可能无法使用索引 ## ORDER BY 多个列但排序方向不同时可能无法使用索引
* **Item**:KEY.008 * **Item**:KEY.008
* **Severity**:L4 * **Severity**:L4
* **Content**:在MySQL 8.0之前当ORDER BY多个列指定的排序方向不同时将无法使用已经建立的索引。 * **Content**:在 MySQL 8.0之前当 ORDER BY 多个列指定的排序方向不同时将无法使用已经建立的索引。
* **Case**: * **Case**:
```sql ```sql
...@@ -786,13 +786,13 @@ CREATE UNIQUE INDEX part_of_name ON customer (name(10)); ...@@ -786,13 +786,13 @@ CREATE UNIQUE INDEX part_of_name ON customer (name(10));
* **Item**:KWR.001 * **Item**:KWR.001
* **Severity**:L2 * **Severity**:L2
* **Content**:因为SQL\_CALC\_FOUND\_ROWS不能很好地扩展,所以可能导致性能问题; 建议业务使用其他策略来替代SQL\_CALC\_FOUND\_ROWS提供的计数功能,比如:分页结果展示等。 * **Content**:因为 SQL\_CALC\_FOUND\_ROWS 不能很好地扩展,所以可能导致性能问题; 建议业务使用其他策略来替代 SQL\_CALC\_FOUND\_ROWS 提供的计数功能,比如:分页结果展示等。
* **Case**: * **Case**:
```sql ```sql
select SQL_CALC_FOUND_ROWS col from tbl where id>1000 select SQL_CALC_FOUND_ROWS col from tbl where id>1000
``` ```
## 不建议使用MySQL关键字做列名或表名 ## 不建议使用 MySQL 关键字做列名或表名
* **Item**:KWR.002 * **Item**:KWR.002
* **Severity**:L2 * **Severity**:L2
...@@ -812,21 +812,21 @@ CREATE TABLE tbl ( `select` int ) ...@@ -812,21 +812,21 @@ CREATE TABLE tbl ( `select` int )
```sql ```sql
CREATE TABLE tbl ( `books` int ) CREATE TABLE tbl ( `books` int )
``` ```
## INSERT INTO xx SELECT加锁粒度较大请谨慎 ## INSERT INTO xx SELECT 加锁粒度较大请谨慎
* **Item**:LCK.001 * **Item**:LCK.001
* **Severity**:L3 * **Severity**:L3
* **Content**:INSERT INTO xx SELECT加锁粒度较大请谨慎 * **Content**:INSERT INTO xx SELECT 加锁粒度较大请谨慎
* **Case**: * **Case**:
```sql ```sql
INSERT INTO tbl SELECT * FROM tbl2; INSERT INTO tbl SELECT * FROM tbl2;
``` ```
## 请慎用INSERT ON DUPLICATE KEY UPDATE ## 请慎用 INSERT ON DUPLICATE KEY UPDATE
* **Item**:LCK.002 * **Item**:LCK.002
* **Severity**:L3 * **Severity**:L3
* **Content**:当主键为自增键时使用INSERT ON DUPLICATE KEY UPDATE可能会导致主键出现大量不连续快速增长,导致主键快速溢出无法继续写入。极端情况下还有可能导致主从数据不一致。 * **Content**:当主键为自增键时使用 INSERT ON DUPLICATE KEY UPDATE 可能会导致主键出现大量不连续快速增长,导致主键快速溢出无法继续写入。极端情况下还有可能导致主从数据不一致。
* **Case**: * **Case**:
```sql ```sql
...@@ -836,7 +836,7 @@ INSERT INTO t1(a,b,c) VALUES (1,2,3) ON DUPLICATE KEY UPDATE c=c+1; ...@@ -836,7 +836,7 @@ INSERT INTO t1(a,b,c) VALUES (1,2,3) ON DUPLICATE KEY UPDATE c=c+1;
* **Item**:LIT.001 * **Item**:LIT.001
* **Severity**:L2 * **Severity**:L2
* **Content**:字符串字面上看起来像IP地址,但不是INET\_ATON()的参数,表示数据被存储为字符而不是整数。将IP地址存储为整数更为有效。 * **Content**:字符串字面上看起来像IP地址,但不是 INET\_ATON() 的参数,表示数据被存储为字符而不是整数。将IP地址存储为整数更为有效。
* **Case**: * **Case**:
```sql ```sql
...@@ -856,13 +856,13 @@ select col1,col2 from tbl where time < 2018-01-10 ...@@ -856,13 +856,13 @@ select col1,col2 from tbl where time < 2018-01-10
* **Item**:LIT.003 * **Item**:LIT.003
* **Severity**:L3 * **Severity**:L3
* **Content**:将ID存储为一个列表,作为VARCHAR/TEXT列,这样能导致性能和数据完整性问题。查询这样的列需要使用模式匹配的表达式。使用逗号分隔的列表来做多表联结查询定位一行数据是极不优雅和耗时的。这将使验证ID更加困难。考虑一下,列表最多支持存放多少数据呢?将ID存储在一张单独的表中,代替使用多值属性,从而每个单独的属性值都可以占据一行。这样交叉表实现了两张表之间的多对多关系。这将更好地简化查询,也更有效地验证ID。 * **Content**:将 ID 存储为一个列表,作为 VARCHAR/TEXT 列,这样能导致性能和数据完整性问题。查询这样的列需要使用模式匹配的表达式。使用逗号分隔的列表来做多表联结查询定位一行数据是极不优雅和耗时的。这将使验证 ID 更加困难。考虑一下,列表最多支持存放多少数据呢?将 ID 存储在一张单独的表中,代替使用多值属性,从而每个单独的属性值都可以占据一行。这样交叉表实现了两张表之间的多对多关系。这将更好地简化查询,也更有效地验证ID。
* **Case**: * **Case**:
```sql ```sql
select c1,c2,c3,c4 from tab1 where col_id REGEXP '[[:<:]]12[[:>:]]' select c1,c2,c3,c4 from tab1 where col_id REGEXP '[[:<:]]12[[:>:]]'
``` ```
## 请使用分号或已设定的DELIMITER结尾 ## 请使用分号或已设定的 DELIMITER 结尾
* **Item**:LIT.004 * **Item**:LIT.004
* **Severity**:L1 * **Severity**:L1
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册