代码如脸面,增强别人眼中你的代码规范
历经无数的案例,由于SQL设计、编写不规范造成后期大量的BUG与性能隐患。
很多团队由于需求First,没精力建设团队统一规范,长期来看这是本末倒置的,它一定会在某一天积累爆发倒逼着你去花N倍的精力去重构…
适用于MySQL5.7及以上版本。在使用C/C++、Java等各种编程语言进行开发活动及运维活动中编写SQL语句时,需要遵循此规范。
开发:负责按照SQL规范进行SQL编码
运维人员:负责根据SQL规范编写运维需要的SQL脚本。
说明:
例外:对于从ORACLE转MySQL的项目,数据库名,表名,字段名可继续保持ORACLE的SQL规范要求,用大写。
即使双表JOIN也要注意表索引、SQL性能。
ps: 超过3个实例表禁止JOIN,如何改写呢? 这个我们可以做适当的冗余设计;或者中间表;
在SQL中尽量不使用LIKE。即使使用也要禁止使用前缀是%的LIKE匹配,因为索引文件具有 B-Tree的最左前缀匹配特性,如果左边的值未确定,那么无法使用此索引。
反例:
索引中有范围查找,那么索引的有序性无法利用。
如:WHERE a>10 ORDER BY b; 索引a_b无法排序。
正例:
WHERE a=? AND b=? ORDER BY c;
索引:a_b_c
如果一本书需要知道第 11章是什么标题,会翻开第 11章对应的那一页吗?目录浏览一下就好,这个目录就是起到覆盖索引的作用。
能够建立索引的种类:主键索引、唯一索引、普通索引,而覆盖索引是查询的一种效果,使用 explain查看结果,extra列会出现:using index。
反例:
SELECT
user,
host
FROM
db AS a
WHERE
a.user = 'mysql.sys';
Note:已知a.user上有单列索引,应用场景中只是为了获取user信息;host为非必须要获取的信息,该host信息查询时需要回表;
正例:
EXPLAIN SELECT
user
FROM
db AS a
WHERE
a. user = 'mysql.sys'
Note:已知a.user上有单列索引,应用场景中只是为了获取user信息,此时可以走索引不需要回表;
MySQL并不是跳过 offset行,而是取 offset+N行,然后返回放弃前 offset行,返回N行,那当 offset特别大的时候,效率就非常的低下,要么控制返回的总页数,要么对超过特定阈值的页数进行 SQL改写。
反例:
SELECT
id,
name
FROM
user_info
WHERE
dep_id = 1
LIMIT 100000,20
正例:
Note:先快速定位需要获取的 id字段,然后再关联:
SELECT
a.id, a.name
FROM
user_info AS a,
(SELECT
id
FROM
user_info
WHERE
dep_id = 1
LIMIT 100000 , 20) AS b
WHERE
a.id = b.id;
1)const 单表中最多只有一个匹配行(主键或者唯一索引),在优化阶段即可读取到数据。
2)ref 指的是使用普通的索引(normal index)。
3)range 对索引进行范围检索。
反例:
explain表的结果,type=index,索引物理文件全扫描,速度非常慢,这个 index级别比较 range还低,与全表扫描是小巫见大巫。
EXPLAIN select
event_id
FROM
zcm_nms.alarm_event a
WHERE
a.alarm_time >= now() - 10
AND a.alarm_time <= now()
正例:
EXPLAIN SELECT
event_id
FROM
zcm_nms.alarm_event a
WHERE
a.event_id=30
字段类型不同会造成隐式转换,导致索引失效。
不要使用 COUNT(列名) 或 COUNT(常量) 来替代 COUNT(),COUNT () 是 SQL92定义的标准统计行数的语法,跟数据库无关,跟 NULL和 非NULL无关。COUNT(DISTINCT col) 计算该列除 NULL之外的不重复行数。
COUNT(*) 会统计值为 NULL的行,而 COUNT(列名)不会统计此列为 NULL值的行。
Note:
可以使用如下方式来避免 SUM()的 NPE问题:
SELECT
IFNULL(SUM(g), 0)
FROM
TABLE;
NULL与任何值的直接比较都为 NULL。
1) NULL<>NULL的返回结果是 NULL,而不是 false。
2) NULL=NULL的返回结果是 NULL,而不是 true。
3) NULL<>1的返回结果是 NULL,而不是 true。
存储过程难以调试和扩展,更没有移植性。
为避免业务逻辑与数据存储发生耦合,禁止使用上述功能,否则不利于后期scale out(扩展)、sharding(分库分表)。
Note:MySQL数据库原生函数可以用,自定义函数不可用。
可以用EXIST代替IN,EXIST在某些场景比IN效率高。
例1:
此场景适应A表数据量大于B表(B表数据量较少),且WHERE后的字段加了索引。这种情况用IN效率高的原因是利用了大表的索引。
SELECT
a.ecs_goods_id, a.ecs_goods_name
FROM
ecs_goods AS a
WHERE
a.cat_id IN (
SELECT
b.cat_id
FROM
ecs_category AS b
);
例2:
此场景适应B表数据量大于A表,且WHERE后的字段加了索引。这种情况用EXISTS效率高的原因是利用了大表的索引。
SELECT
a.ecs_goods_id, a.ecs_goods_name
FROM
ecs_goods AS a
WHERE
EXISTS (
SELECT
cat_id
FROM
ecs_category AS b
WHERE
a.cat_id = b.cat_id
);
TRUNCATE TABLE可能会造成生产的性能事故和安全事故。
如若不加WHERE条件,则是对全表进行删除、更新操作,可能会引起非常严重的后果,所以必须要加上相应的WHERE条件方可。
Note:如果不带WHERE条件的DELETE操作,会将表中所有记录都删除。如果表中数据量过大,也可能会造成性能事故。
禁止使用跨库(跨schema)查询,方便后续分库分表。
提示:本条目与ORACLE开发规范有差异
反例:
SELECT
SUM(a.amount)
FROM
payment AS a,
acct_book AS b
WHERE
a.payment_id = b.acct_book_id
AND b.acct_id = ?;
Note:payment和acct_book都是大表,不建议关联查询
正例:
SELECT
b.acct_book_id
FROM
acct_book AS b
WHERE
b.acct_id = ?;
SELECT
SUM(a.amount)
FROM
payment AS a
WHERE
a.payment_id IN (?);
Note:payment和acct_book都是大表,拆成单表简单查询
MySQL 中OR的效率比IN低很多
大数据量,过滤条件未加索引,且事先知道结果只需要一条记录时使用LIMIT 1,可加快执行效率。
SELECT
cust_name
FROM
cust
WHERE
email = ? LIMIT1
Note:email字段上无索引,找到一条记录后即返回
SELECT
txn_type_id,
amount
FROM
bc_transaction
WHERE
acct_id = ?;
Note:代码里面根据txn_type_id对金额进行汇总。
数据库不善于反向查找,故不建议使用NOT IN。
UNION在进行表链接后会筛选掉重复的记录,所以在表链接后会对所产生的结果集进行排序运算,删除重复的记录再返回结果,如果表数据量大的话可能会导致用磁盘进行排序。
UNION ALL操作只是简单的将两个结果合并后就返回,所以可能存在重复记录。
需要结合业务需求分析使用UNION ALL的可行性。
mysql> SELECT id,name,age FROM std1 WHERE 1=1;
+----+-------+-----+
| id | name | age |
+----+-------+-----+
| 11 | May | 18 |
| 12 | Jane | 15 |
| 13 | Sunny | 19 |
+----+-------+-----+
3 rows in set
mysql> SELECT id,name,age FROM std2 WHERE 1=1;
+----+------+-----+
| id | name | age |
+----+------+-----+
| 11 | May | 18 |
| 18 | Anni | 16 |
+----+------+-----+
2 rows in set
mysql> SELECT id,name,age FROM std1
UNION
SELECT id,name,age FROM std2;
+----+-------+-----+
| id | name | age |
+----+-------+-----+
| 11 | May | 18 |
| 12 | Jane | 15 |
| 13 | Sunny | 19 |
| 18 | Anni | 16 |
+----+-------+-----+
4 rows in set
mysql> SELECT id,name,age FROM std1
UNION ALL
SELECT id,name,age FROM std2;
+----+-------+-----+
| id | name | age |
+----+-------+-----+
| 11 | May | 18 |
| 12 | Jane | 15 |
| 13 | Sunny | 19 |
| 11 | May | 18 |
| 18 | Anni | 16 |
+----+-------+-----+
5 rows in set
Note:上述举例说明UNION ALL和UNION对于查询结果的区别,请结合实际场景合理选择。
反例:
SELECT
acc_nbr,
acct_id
FROM
subs;
Note:
a.当需要查询表中的所有列时,也需列出所有的字段名。
b.例外:如果有子查询,而且子查询有列名的,可以使用SELECT *。目前框架的分页是对业务侧SQL进行了一层包装,必须要select * ,因为框架不知道业务SQL细节。 这个规则在框架侧可以例外。
Java代码中使用 prepared statement 对象,只传参数,比传递 SQL 语句更高效;一次解析,多次使用;降低SQL 注入概率。
反例:
String sql = "SELECT id,name FROM tb_name WHERE name= '"+varname+"' AND passwd='"+varpasswd+"'";
(1)如果我们把[' or '1' = '1]作为varpasswd传入进来.用户名随意
SELECT id,name FROM tb_name WHERE name= '随意' AND passwd = '' or '1' = '1';
因为'1'='1'肯定成立,所以可以任何通过验证.
(2)如果把[';drop table tb_name;]作为varpasswd传入进来,则:
SELECT * FROM tb_name WHERE name= '随意' AND passwd = '';drop table tb_name;
MySQL数据库可以使这些语句得到执行。
正例:
perstmt = con.prepareStatement("insert into tb_name (col1,col2,col2,col4) values (?,?,?,?)");
perstmt.setString(1,var1);
perstmt.setString(2,var2);
perstmt.setString(3,var3);
perstmt.setString(4,var4);
perstmt.executeUpdate();
ORDER BY RAND() 生成随机结果,会降低查询效率.
反例:
UPDATE subs AS s
LEFT JOIN acct AS a ON s.acct_id = a.acct_id
SET s.prefix = ‘0086’
SET e.salary = 10000,
t.name= ‘ABC’
WHERE
e.id = 3
AND t.id = 1;
反例:
SELECT
acct_item_type,
charge
FROM
acct_item AS a
WHERE
acct_id = ?
AND acct_item_type_id = 3 FOR UPDATE;
UPDATE subs AS s
SET s.update_date = SYSDATE - 1
WHERE
s.subs_id = ?;
NOTE: Oracle中SYSDATE – 1表示当前时间减1天;MySQL中SYSDATE() – 1表示当前时间减1秒。这种数据库间的差异已经导致过程序故障。
正例:
UPDATE subs AS s
SET s.update_date = ?
WHERE
s.subs_id = ?;
Note: SQL中需要传入实例化后的时间参数。
PS: 某些平台需要部署到不同的客户环境所以需要考虑到各个数据库之间的兼容
避免使用HAVING子句,HAVING只会在检索出所有记录之后才对结果集进行过滤,这个处理需要排序、统计等操作。如果能通过WHERE子句限制记录的数目,那就能减少这方面的开销。
-- 反例:
SELECT
p.product_type,
COUNT(*)
FROM
product AS p
GROUP BY
p.product_type
HAVING
2 = COUNT(*);
-- 正例:
SELECT
p.product_type,
COUNT(*)
FROM
product AS p
GROUP BY
p.product_type
HAVING
COUNT(*) = 2;
-- 反例
SELECT
s.cam_id
c.cam_name,
AVG(s.id) AS studentAvg
FROM
Students AS s
JOIN campus AS c ON c.cam_id = s.cam_id
GROUP BY
s.cam_id,
c.cam_name
HAVING
AVG (s.id) > 2200000
ORDER BY
s.cam_id;
-- 正例:
SELECT
s.cam_id,
c.cam_name,
AVG (s.id) AS studentAvg
FROM
Students AS s
JOIN campus AS c ON c.cam_id = s.cam_id
GROUP BY
s.cam_id,
c.cam_name
HAVING
studentAvg > 2200000
ORDER BY
s.cam_id;
-- 反例
SELECT
a.deptno,
AVG(a.sal)
FROM
emp AS a
GROUP BY
a.deptno
HAVING
a. AVG (sal) > (SELECT b. AVG (sal) FROM emp AS b);
-- 正例
SELECT
a.deptno,
AVG(a.sal)
FROM
emp AS a
GROUP BY
a.deptno
HAVING
a. AVG (sal) > 15000;
全局表:
在业务系统中,往往存在大量的类似字典表的数据库表,这类表的数据量一般较小,变化不频繁,如:字典、配置、工号、基表、区域等,这类表定义为全局表,即在每个库都保存一份完整的相同数据,全局表就是用于解决这一类表的跨库关联查询问题。全局表也叫广播表。
分片表
分片(水平)是根据某种规则将数据分散至多个库中,每个分片仅包含数据的一部分。这类表即为分片表,这些库即为分片。
库内分表
逻辑表在同一个数据库实例的同一个schema内进行分表,以解决单表数据量过大、分片数量过多和跨分片事务的问题。例如将goods表分成多个子表,分别为goods_0, goods_1, goods_2……可用于替换MySQL的分区表。库内分表和水平分库组合使用。狭义的库内分表是不分片纯库内分表。
分片键
用于分片的字段
pass
pass
pass
pass