MySQL数据库操作规范

文档说明:
该文档旨在对MySQL数据库的使用做一个统一的约定和规范;以便使大家更明确、更有效的用好数据库,最终使数据库发挥更好的作用,从而提升产品的质量。

一、基础规范

1.1、【强制】使用InnoDB存储引擎。
解读:InnoDB存储引擎是MySQL默认存储引擎,支持事务和行级锁,并发性能更好,CPU及内存缓存页优化使得资源利用率更高,并且MyISAM在8.0中考虑移除了。

1.2、【强制】使用统一的字符集(utf8或utf8mb4),如果有存储emoji表情之类的,则需要使用utf8mb4,否则使用utf8即可。
解读:无需转码,无乱码风险;utf8mb4向下兼容utf8但存储使用的空间会比utf8略大。

1.3、【强制】表、字段必须加入中文注释,注释要言简意赅。
解读:便于识别表和字段的用途,有利于维护;
反例:t_company_organization_scope_employee 可以采用缩写改成t_com_org_scope_emp

1.4、【强制】禁止使用存储过程、视图、触发器、Event。
解读:高并发大数据的互联网业务,架构设计思路是“解放数据库CPU,将计算转移到服务层”,并发量大的情况下,这些功能很可能将数据库拖死,业务逻辑放到服务层具备更好的扩展性,能够轻易实现“增机器就加性能”。数据库擅长存储与索引,CPU计算还是上移到业务层。放到业务层也便于管理和维护。

1.5、【强制】禁止存储大文件或者图片。
解读:大文件和图片存储在文件系统,数据库里存URL信息。

二、数据库设计规范

1、命名规范

1.1【强制】库名、表名、字段名:必须使用小写字母,下划线风格,名称要简洁明了,长度不超过32个字符。
禁止数字开头,禁止两个下划线中间只出现数字,禁止复数名词和驼峰命名,禁止出现大写或中文,禁止使用中划线。
解读:
正例:getter_admin,task_config,level3_name
反例:GetterAdmin,taskConfig,level_3_name
备注:对于中划线,当前公司项目的库名已经使用了,处于更改成本考虑可以统一使用中划线,但表名、字段名仍然禁止使用中划线。

1.2【建议】对同一业务模块或关联功能的表应当使用相同前缀来区分。
解读:统一的命名规则便于表格的使用和维护。
正例:如acl_xxx,house_xxx,user_xxx;其中前缀通常为这个表的模块或依赖主实体对象的名字,通常来讲表名为:业务动作类型,或是业务类型;

1.3【强制】命名中不允许出现MYSQL数据库中的保留字。如desc、range、match、delayed、date、now等,请参考MySQL官方保留字:
https://dev.mysql.com/doc/refman/5.7/en/keywords.html

1.4【建议】索引命名格式为:索引类型_字段名缩写。普通索引名idx_xxx,唯一索引名un_xxx。
解读:
统一风格便于使用和维护。

2、表设计规范

2.1【强制】单表中列的数量必须小于30,单库表的数量要控制在500个以内。
解读:
通常来说列数越多,物理文件越大,表的效率越差。
库中表越多,物理文件就越多,空间消耗就越大。

2.2【强制】表中必须明确指定主键,无特殊情况则要使用自增的UNSIGNED BIGINT型主键。
解读:主键的递增可以使数据行在物理文件中按顺存放,可以避免page分裂,减少表碎片的产生提升空间和内存的使用,继而提高写入、查询的性能。

2.3【强制】禁止使用外键,如果有外键完整性约束,需要应用程序控制。
解读:外键会导致表与表之间耦合,UPDATE与DELETE操作都会涉及相关联的表,十分影响sql的性能,甚至会造成死锁。

3、字段设计规范

3.1【强制】表的各个字段必须要设置NOT NULL约束,特别是作为过滤条件的列!
解读:
1)所有NULL值在索引中对于一个KEY,在MySQL5.6.17及之后,IS NULL等同于等值查询,可以用索引;
2)NULL字段在物理文件上是通过打标签的方式记录的,对于这种类型MySQL内部需要进行特殊处理,增加数据库处理记录的复杂性;同等条件下,表中有较多个NULL值字段的时候,数据库的处理性能会降低很多;
3)NULL字段的查询语句优化空间不大,对NULL的处理只能采用IS NULL或IS NOT NULL,而且无法使用索引;

3.2【建议】根据实际需要来为字段设置默认值。
解读:
默认值可以通过自动补充列值的方式,在列值不全的情况下可成功向表中写入数据;防止了人为疏忽而导致程序失败的可能。但这也恰恰是埋下了雷,使得在程序异常时很难发现问题。
为了程序更加健壮,推荐在设置了NOT NULL的前提下不提供默认值,直接报错后进行处理。

如果需要设置默认值约束请使用以下默认值:
TINYINT/SMALLINT/INT/BIGINT 整数类型默认值:0
CHAR/VARCHAR 字符类型默认值:'' (空字符串)
DATE 类型默认值:'0000-00-00'
TIME 类型默认值:'00:00:00'
DATETIME 类型默认值:'0000-00-00 00:00:00'

注意:
有一种误区:只要指定了默认值就OK了,NOT NULL就是多余的;在插入时对相应列赋值为NULL时插入表里会自动填充默认值的。
这个认识是错误的,如果没有NOT NULL,即使有默认值在插入NULL时也不会填充默认值(自增主键除外)。
例如:

CREATE TABLE t1(
id      UNSIGNED BIGINT NOT NULL AUTO_INCREMENT ,
name VARCHAR(10) DEFAULT 'xxx' COMMENT '姓名',
age    UNSIGNED SMALLINT DEFAULT 0 COMMENT '年龄',
PRIMARY KEY(id)
)ENGINE=INNODB COMMENT='用户信息' ;
mysql> INSERT INTO aa (id,NAME,age) VALUES(NULL,NULL,1);

结果:

mysql> select * from t1;
+----+------+------+
| id | name | age  |
+----+------+------+
|  1 | NULL |    1 |
+----+------+------+
1 rows in set (0.00 sec)

看到了吧,name虽然有默认值'xxx'但是在指定NULL后,并没有用默认值填充!!

3.3【强制】使用UNSIGNED存储非负整数。
解读:
可以扩大数值的使用范围,减少范围查找时MySQL无意义的负值查找、比对的资源浪费。

3.4【强制】小数类型用DECIMAL或者对数值扩大后使用int/bigint 类型来存储,禁止使用FLOAT和DOUBLE。
解读:
FLOAT和DOUBLE在存储、计算的时候,存在精度损失的问题,很可能在值的比较时,得到不正确结果。如果存储的数据范围超过DECIMAL的范围,建议将数据拆成整数和小数分开存储。
对于数值精度要求高的场景,特别是在存储货币的场景中通常是通过将‘元’换成‘分’后进行整数存储,在读取或写入的时候,进行转换。

3.5【强制】枚举类型禁止使用ENUM,可使用TINYINT代替。
解读:
a)增加新的ENUM值要做DDL操作;
b)ENUM的内部实际存储就是整数;

3.6【强制】如果存储的字符串长度几乎相等,请使用定长字符串CHAR类型。
解读:
在物理文件中定长的字符串是使用统一大小的空间存放的,这在查询时MySQL可以使用统一的偏移量来获取数据,提升的性能。

3.7【建议】VARCHAR是可变长字符串,一定要根据实际情况按需设置长度;长度最好不要超过250个汉字字符(utf8编码)。
解读:
当VARCHAR存放的字符过多时,在物理文件中存放时会产生行溢出现象;这会影响性能。
选择合适的字符存储长度,不但节约数据库表空间、节约索引存储,更重要的是提升检索速度。

3.8【建议】使用VARCHAR存储电话号码。
解读:
a)涉及到区号或者国家代号,可能出现+-();
b)电话号码不会做数学运算;
c)VARCHAR可以支持模糊查询,例如:LIKE '138%';

3.9【建议】网络IP字段,除特殊情况外一律用INT UNSIGNED来记录(IP可通过INET_ATON函数转换为数值)。
解读:
将IP地址转换为数值来存取,有利于性能提升。

3.10【强制】禁止使用TEXT、BLOB类型,如要使用可以其将内容垂直拆分到子表中。
解读:
TEXT、BLOB这些大类型在物理存储时会使用行溢出的方式来存储,这会浪费更多的磁盘和内存空间。在从物理文件中读取这类对象时会额外消耗资源,而且大量的大字段在查询时会将内存中的大量热数据淘汰掉,导致内存命中率急剧降低,影响数据库性能。
如果一定要使用这类对象,可以将这些字段拆分出去,单独存放;这样保证了主表的瘦小。

3.11【建议】字段允许适当冗余,以提高性能,但是必须考虑冗余数据的同步情况。
解读:
字段的冗余可以减少表之间的关联,使用得当可以提升性能。
冗余字段应遵循:不是频繁修改的字段,不是VARCHAR超长字段,更不能是TEXT字段。

3.12【强制】禁止在数据库中存储明文密码,把密码加密后存储。

4、索引设计规范

说明:区分度是指列中存放的数据值中唯一值个数占中总值个数的比例。

4.1【建议】禁止在更新十分频繁、区分度不高的属性上建立索引。
解读:
a)更新会变更B+树,更新频繁的字段建立索引会大大降低数据库性能;
b)"性别"这种区分度不大的属性,建立索引是没有什么意义的,不能有效过滤数据,性能与全表扫描类似;除非数据存在严重倾斜,并且刚好只查询那部分小范围数据时才考虑建立。

4.2【建议】业务上具有唯一特性的字段(即使是组合字段的唯一),必须建立唯一索引。
解读:不要以为唯一索引影响了INSERT速度,这个速度损耗可以忽略,但提高查找速度是明显的;另外,即使在应用层做了非常完善的校验和控制,也要做唯一索引。

4.3【建议】在VARCHAR字段上建立索引时,必须指定索引长度,没必要对全字段建立索引,根据实际文本区分度决定索引长度。
解读:索引的长度与区分度是一对矛盾体,一般对字符串类型数据,长度为20的索引,区分度会高达90%以上,可以使用COUNT(DISTINCT LEFT(列名, 索引长度))/COUNT(主键)的区分度来确定。

4.4【建议】索引数量应不超过列总数的40%(一般单表索引建议控制在5个以内)。
解读:索引过多会增加存储开销和增删改的开销。

4.5【建议】尽量使用组合索引,建立组合索引时必须把区分度高的、使用频率高的字段放在前面,索引中字段数不允许超过3个。
解读:能够更加有效的过滤数据,索引上字段超过3个时,实际的过滤数据效果已经不好了,而且还占用了空间。

4.6【强制】在排序、分组、取唯一的字段上创建索引,经常与其他表进行关联的表,在关联字段上应该建立索引,经常出现在WHERE子句中的字段,特别是大表的字段,应该建立索引。

三、SQL使用规范

1、SQL书写规则

1.1【建议】SQL语句的大小写风格要统一。
SQL语句中出现的所有表名、表别名、字段名等自定义数据库对象都应小写。
SQL语句中出现的系统保留字、内置函数名、SQL保留字等都应大写,不建议使用保留字。
解读:
大小写区分开,便于对象的识别;
如:
SELECT c1,c2 FROM tab WHERE c1='xxx';
INSERT INTO tab(c1,c2,c3) VALUES ('a','b',30);

1.2【强制】禁止使用MySQL特有的非标准SQL语法,所有SQL都必须使用标准写法。
解读:
MySQL支持多种非标准SQL语法,这会使得SQL书写变得混乱,难以维护;所以一定要按标准SQL来书写。

MySQL支持的非标准INSERT语法:
1)INSERT INTO employees SET employee_name='John',date='2018-06-15',mployee_age=30;
2)INSERT INTO employees(employee_name,date,mployee_age)
VALUES ('John','2018-06-15',30)
ON DUPLICATE KEY UPDATE ;

标准写法:
INSERT INTO employees(employee_name,date,mployee_age)
VALUES ('John','2018-06-15',30);

1.3【建议】SQL语句中表的别名应简短明了,宜反映表名的实际意义。
解读:表名比较长的时候一定要使用别名来优化SQL书写方式;这样的好处有:
1)方便表对象的引用;
2)更有利于SQL的阅读、管理;
3)减小慢SQL被截断的可能(慢SQL以表方式存放的情况);
4)最重要的还可以节省数据库内存。

1.4【建议】同一项目的SQL书写格式应该统一。

2、可读性规则

2.1【强制】不允许使用SELECT ,必须指定列名;需要什么就索取什么。
解读:
a)对
的解析以及读取那些不需要的列会增加CPU、IO、NET消耗;
b)不能有效的利用覆盖索引;
c)使用SELECT *容易在增加或者删除字段后出现程序BUG;

2.2【强制】INSERT必须明确指定插入的字段名。
解读:
避免在增加或者删除字段后出现程序BUG。
正例:INSERT INTO tab(c1,c2,c3) VALUES ('a','b',30);

2.3【强制】不等于统一使用"<>"。
SQL认为"<>"和"!="是等价的,都代表不等于的意义。为了统一,不等于一律使用"!="表示。

2.4【强制】在表连接时要对表设置别名,别名要简洁明了,控制在5个英文字符内,不易过长。

2.5【强制】应避免写复杂的SQL语句。
解读:
a) 增加SQL可读性;
b) 复杂SQL往往效率不是很好。

2.6【强制】建议不用now(),uuid()等函数来填充SQL。
解读:
在MySQL使用函数来计算结果值,会消耗MySQL的CPU资源。
如:INSERT INTO tab(a,b,c) VALUES('aa','bb',NOW()); 建议:c的值直接从前端传入。

3、性能相关规则

3.1【强制】避免在数据库中进⾏数学运算(MySQL不擅长数学运算和逻辑判断)

3.2【强制】SQL语句应避免对大表的全表扫描操作,对大表的操作应尽量使用索引。

3.3【强制】SQL语句应避免’硬’删除的操作,应该采用修改状态的’软’删除。
解读:
频繁的物理删除会使表的碎片增多,影响性能。

3.4【强制】应按照业务需要使用事务,同时应保持事务简短,避免大事务,确保整个事务涉及的数据库对象不要超过5个,执行时间不要超过3秒。

3.5【强制】每个SQL返回结果的行数不能太多,用多少取多少,要控制在500行以内。统计分析除外。

3.6【强制】在事务完整性的基础上,SQL语句应在程序中显式使用 COMMIT,ROLLBACK,尽快提交事务,释放系统资源。

3.7【强制】对大量数据的更新要打散后批量更新,不要一次更新太多数据。(大事务)

3.8【强制】对大量数据插入时不要使用逐条的INSERT语句进行插入,要使用合并插入的方式。
解读:因为MySQL默认开启了自动提交,如果一条条执行就意味着每条结束后都有执行一次COMMIT,这样严重影响性能。合并插入则是一次COMMIT。
低效:
INSERT INTO emp (empno,ename,deptno) VALUES(1,'a',10);
INSERT INTO emp (empno,ename,deptno) VALUES(2,'b',20);
INSERT INTO emp (empno,ename,deptno) VALUES(3,'c',30);
INSERT INTO emp (empno,ename,deptno) VALUES(4,'d',10);
INSERT INTO emp (empno,ename,deptno) VALUES(5,'e',10);
INSERT INTO emp (empno,ename,deptno) VALUES(6,'f',10);

高效:
INSERT INTO emp (empno,ename,deptno)
Values (1,'a',10),(2,'b',20),(3,'c',30), (4,'d',10), (5,'e',10),(6,'f',10);

3.9【强制】禁止使用属性的隐式转换。
解读:
隐式转换会导致索引失效,
例如:
t_user的phone是VARCHAR类型,且有索引;
SELECT uid FROM t_user WHERE phone=13812345678; 会导致全表扫描,而不能命中phone索引,因为发生了数值到字符串的隐式转换。

3.10【强制】去掉where 1=1 这样无意义或恒真的条件,如果遇到update/delete或遭到sql注入就恐怖了。

3.11【建议】减少子查询的使用。
解读:子查询除了可读性差之外,通常会在一定程度上影响了SQL运行效率. 应尽量减少子查询的使用,采用关联或其他效率更高、可读性更好的方式实现。

3.12【建议】禁止在WHERE条件的列上使用函数或者表达式,要将其改写到等号右边。
解读:
在过滤条件的列上使用函数,会导致列上的索引无法被使用;
错误:SELECT uid FROM t_user WHERE DATE(day)='2017-02-15'; ==>会导致全表扫描
正确:SELECT uid FROM t_user WHERE day>='2017-02-15 00:00:00' and day<='2017-02-15 23:59:59'

3.13【建议】禁止负向查询,以及%或_开头的模糊查询。
解读:反向操作是不会用到索引的。

a)负向查询条件:NOT、<>、<>、!<、!>、NOT IN、NOT LIKE等,会导致全表扫描;
b)%或_开头的模糊查询,会导致全表扫描;

3.14【建议】SQL语句应避免不必要的分组和排序。
解读:分组和排序操作会用到临时表,影响性能。

3.15【建议】SQL语句尽可能避免多表联合的复杂查询。

3.16【建议】禁止对大表进行关联查询,禁止大表使用子查询
解读:关联会产生临时表,而且大表的数据量大,会进一步消耗更多内存与CPU,极大影响数据库性能。

3.17【建议】禁止使用OR条件,如果是同一列的不同值的OR语句可以改为IN查询。
解读:MySQL在执行时内部会对这类OR语句改写为IN语句,如果我们人为的将最终SQL改写成IN,那MySQL就不需要消耗资源去做转换了。
如:
错误:SELECT c1,c2 FROM tab WHERE c3 =1 OR c3 = 2;
正确:SELECT c1,c2 FROM tab WHERE c3 IN (1,2);

3.18【建议】禁止使用OR条件,如果是不同列的OR语句可以考虑用UNION替换OR。
解读:
将OR运算的逻辑判断使用分条件查询来实现,可以很好的提高查询效率。

低效:
SELECT loc_id , loc_desc , region
FROM location
WHERE loc_id = 10 OR region = 'MELBOURNE';

高效:
SELECT loc_id , loc_desc , region
FROM location
WHERE loc_id = 10
UNION
SELECT loc_id , loc_desc , region
FROM location
WHERE region = 'MELBOURNE';

3.19【建议】IN里包含的值的个数建议控制在100以内,过多IN的效率不高。

3.20【建议】在使用union时优先考虑使用union all,少使用union。
解读:
union all不去重,少了排序操作,速度相对比union要快,如果没有去重的需求,优先使用union all。

3.21【建议】用>=替代>,用<=代替<,帮助MySQL确定下限和上限。
解读:
如果不使用’=’指定上下限,MySQL需要自己去分析查找这个边界值,浪费了资源。

4、其他建议

4.1【强制】页面搜索严禁左模糊或者全模糊,如果需要请走搜索引擎来解决。
解读:索引具有B-Tree的最左前缀匹配特性,如果左边的值未确定,那么无法使用此索引。

4.2【强制】应用程序必须捕获SQL异常,并有相应处理.

4.3【强制】应用程序要合理配置重连、链接过期时间,数据库对空闲链接默认超时时间是8小时(超过这个时间的会被杀掉),但对于被频繁连接的前端业务库,通常线上往往设置在30分,而新建连接不频繁后端通常会相应调长一点,大概1-3小时;这样可以使空闲的链接及早退出从而释放数据库内存资源。

4.4【强制】不同业务模块必须使用不同的账号来连接数据库,便于问题排查和权限管理