MySQL数据库规范 (设计规范+开发规范+操作规范)
I 文档定义
1.1 编写目的
为了在软件生命周期内规范数据库相关的需求分析、设计、开发、测试、运维工作,便于不同团队之间的沟通协调,以及在相关规范上达成共识,提升相关环节的工作效率和系统的可维护性。同时好的规范,在执行的时候可以培养出好的习惯,好的习惯是软件质量的保证。
1.2 适用范围
本文档适用于开发、测试、QA及运维团队成员。
II . 命名设计规范
2.1 总则
(1)所有命名采用26个英文小写字母和0-9这十个自然数,加上下划线_组成。不能出现其他字符(注释除外)。
(2)对象名尽量短,长度不超过30个字符。
(3)对象名字尽量描述实体的内容,由英文单词、单词组合或单词缩写组成,不以数字和_开头。
(4)命名中禁止使用SQL保留字。
2.2 库名
库名与应用名称尽量一致,统一小写,以下划线分割。
2.3 表名
表名必须使用小写字母或数字,以下划线分割,禁止出现数字开头,禁止两个下划线中间只出现数字。如果表名仅有一个单词,那么建议不使用缩写,而是用完整的单词。同一模块的表尽可能使用相同的前缀,表名称尽可能表达含义。
数据表 <模块标识>_<表标识> 例如: order_header , order_detail
编码表 base_<模块标识>_<表标识>
日志表 log_<模块标识>_<表标识>
2.4 字段名
(1) 能表达字段功能的英文单词或单词缩写,一般不超过三个英文单词,以下划线分割。布尔类型的字段以“is_”作为前缀。
(2) 各表之间意义相同的字段应同名。
(3) 系统中所有属于内码的字段(仅用于表示唯一性和程序内部用到的标识性字段),名称取为:<表标识>_id。
(4) 系统中属于是业务范围内的编号的字段,其代表一定的业务信息,这样的字段建议命名为<业务标识>_code,其数据类型为VARCHAR,该字段需加唯一索引。
(5) 字段名不要与表名重复。
(6) 不要在列的名称中包含数据类型。
(7) 每个字段添加字段说明。
(8) 数据库字段名的修改代价很大,所以字段名称需要慎重考虑。
(9) 统一命名字段:create_by、create_time、modify_by、modify_time、disabled
2.5 索引名
A. 非唯一索引必须按照“idx_<构成索引的字段名>”进行命名
例如:在age上添加索引idx_age
B. 唯一索引必须按照“uidx_<构成索引的字段名>”进行命名
例如:uidx_cardid
C. 组合索引建议包含所有字段名,过长的字段名可以采⽤缩写形式
例如:idx_age_name
2.6 视图命名
v_<模块标识>_<视图标识>
2.7 存储过程命名
usp_<模块标识>_<存储过程标识>
2.8 函数命名
ufn_<模块标识>_<函数标识>
III 数据库设计规范
3.1 表设计原则
(1) 表的存储引擎建议是InnoDB存储引擎,InnoDB 支持事务,支持行级锁,更好的恢复性,高并发下性能更好
(2)同一个DB中的表,其存储引擎、字符集应保持统一
(2) 数据表创建、变更具备说明文档
数据表创建、变更时必须提供数据表设计文档: 包含表及字段详细说明
(3) 规范化与反规范化
规范化的优点是减少了数据冗余,节约了存储空间,相应逻辑和物理的I/O次数减少,同时加快了增、删、改的速度。但是一个完全规范化的设计并不总能生成最优的性能,因为对数据库查询通常需要更多的连接操作,从而影响到查询的速度,而且范式越高性能就会越差。出于性能和方便管理的考虑,原则上表设计应满足第三范式。有时为了提高某些查询或应用的性能而可以破坏规范规则,即反规范化。数据应当按两种类别进行组织:频繁访问的数据和频繁修改的数据。对于频繁访问但是不频繁修改的数据,内部设计应当物理不规范化。对于频繁修改但并不频繁访问的数据,内部设计应当物理规范化。比较复杂的方法是将规范化的表作为逻辑数据库设计的基础,然后再根据整个应用系统的需要,物理地非规范化数据。
(4)临时库表必须以 _tmp_ 为前缀并以日期为后缀,备份表必须以 _bak_ 为前缀并以日期 为后缀。
(5)尽量控制单表数据量的大小,建议控制在 600 万以内
大表在查询性能和结构修改、备份、恢复等运维方面存在很多弊端。可以用历史数据归档,分库分表、选择其它类型数据库等手段来控制数据量大小。
(6)数据表分类说明
根据应用的实际需要和特点,可以将数据表进行如下分类:
A. 基本数据表:描述业务实体的基本信息。例如:人员基本信息、单位基本信息等。
B. 标准编码表:描述属性的列表值。例如:职称、民族、状态等。
C. 业务数据表:记录业务发生的过程和结果。例如:人员调动登记、变更通知单等。
D. 系统信息表:存放与系统操作、业务控制有关的参数。例如:用户信息、权限、用户配置信息等。
E. 统计数据表:存放业务数据统计值。例如:通知单统计、人员类别统计等。
F. 临时处理表:存放业务处理过程中的中间结果。
G. 其他类型表:存放应用层的日志、消息记录等。
3.2 字段设计原则
(1)完善的字段说明
涉及数据字段新增、变更,必须提供字段说明,需要及时更新字段注释。
(2)选择符合存储需要的最小的数据类型
一般来说,应该使用能正确存储和表示数据的最小类型。如果不确定需要什么数据类型,则选择不会超出范围的最小类型。选择更简单的数据类型。例如,整数类型的比较其代价小于字符类型的比较,因为字符集和排序规则使字符比较更复杂。
(3)合理的字段默认值
字段尽可能有默认值,字符型的默认值为一个空字符串,数字型的默认为数值0。 尽可能把字段定义为NOT NULL。对于字段能否NULL,应该在SQL建表脚本中明确指明,不应使用缺省。
(4)所有布尔类型字段数据类型是unsigned tinyint,数值0表示为假;数值1表示为真(根据表的字段意义:比如Disabled = 1表示 Disabled 值为真,可以表示数据被逻辑删除)
(5)避免使用 ENUM 类型
ENUM 类型的 ORDER BY 操作效率低,需要额外操作。
(6)MySQL最大行大小不能超过64KB(65535字节),所以一个表中的字段不要太多,理论上建议不要超过30个。
(7)如果存储的字符串长度几乎相等,推荐使用CHAR定长字符串类型。
(8)VARCHAR是可变长字符串,不预先分配存储空间,长度不要超过2000,如果存储长度大于此值,定义字段类型为text或blob,独立出来一张表,用主键来对应,避免影响其他字段索引效率。TEXT 和 BLOB 的主要差别是 BLOB 能够保存二进制数据
;而 TEXT 只能保存字符数据。在程序设计时,
尽可能不使用TEXT、BLOB类型。
(9)区分使用DATETIME和TIMESTAMP,两者都可用来表示YYYY-MM-DD HH:MM:SS类型的日期。两种都保存日期和时间信息,毫秒部分最高精确度都是6位数。建议使用TIMESTAMP(3)。
A. TIMESTAMP占用4字节,DATETIME占用8字节,当保存毫秒部分时两者都使用额外的空间 (1-3 字节)。
B. TIMESTAMP的取值范围比DATETIME小得多,不适合存放比较久远的日期。TIMESTAMP只能存储从 '1970-01-01 00:00:01.000000' 到 '2038-01-19 03:14:07.999999' 之间的时间。而DATETIME允许存储从 '1000-01-01 00:00:00.000000' 到 '9999-12-31 23:59:59.999999' 之间的时间。
C. TIMESTAMP的插入和查询受时区的影响。如果记录的日期需要让不同时区的人使用,最好使用 TIMESTAMP。
(10)根据实际需要选择能够满足应用的最小存储的日期类型。如果应用只需记录“年份”,那么用1个字节的YEAR类型完全可以满足,而不需要用4个字节来存储的DATE类型。这样不仅可以节约存储,还可以提高表的操作效率。
(11)小数类型为decimal,禁止使用float和double。因为float和double在存储的时候,存在精度损失问题,这是浮点数特有的问题。因此在精度要求比较高的应用中(比如货币)要使用定点数而不是浮点数来保存数据。浮点数指的就是含有小数的值,浮点数插入到指定列中超过指定精度后,浮点数会四舍五入,MySQL 中的浮点数指的就是 float
和 double
,定点数指的是 decimal
,定点数能够更加精确的保存和显示数据。
(12)字段允许适当冗余,以提高性能,但是必须考虑数据完整性。冗余字段应遵循:
A. 不是频繁修改的字段。
B. 不是varchar超长字段,更不能是text字段。
C. 需要维护冗余字段的数据完整性。
3.3 主键设计原则
(1)一定要有显式的主键。
(2)针对InnoDB,在无特殊需求的情况下,建议使用与业务无关的自增ID作为主键。
(3)自增字段做主键时,字段类型必须是bigint 。
(4)不推荐使用联合主键。由于InnoDB索引的数据结构都是B+tree,对包含联合主键的表做大量写入,会导致InnoDB为了维持B+tree而移动大量数据,降低性能。
(5)禁止外键。对性能损耗特别大,一般的做法是,在业务层设计专门的逻辑或解决方案来保证数据的一致性,以最终一致的时差来换取即使访问的性能问题。
3.4 索引设计原则
(1)不允许存在和主键重复的索引。主键其实就是一个非空的唯一索引,所以再在该字段上添加一个索引完全是多此一举。
(2)业务上具有唯一特性的字段,即使是组合字段,也必须建成唯一索引。唯一索引的值是唯一的,可以更快速地通过该索引确定某条记录。另外,即使在应用层做了非常完善的校验控制,只要没有唯一索引,必然有脏数据产生。
(3)考虑索引列值的分布。评估某一栏位是否值得建索引,是根据选择性(符合条件笔数/总笔数)*100%来判断,选择性越低代表越值得,惯用的百分比界线是20%。如果某个数据列用于记录性别(只有"M"和"F"两种值),并且值出现的几率几乎相等,那么无论搜索哪个值都可能得到一半的数据行,在这种情况下索引的用处就不大。因为查询优化器发现某个值出现在表的数据行中的百分比很高的时候,它一般会忽略索引,进行全表扫描。
(4)为经常需要排序、分组和关联的字段建立索引。
(5)为常作为查询条件的字段建立索引。
(6)使用短索引,不要索引大字段。如果对varchar字段进行索引,必须指定一个前缀长度,尽量使用前缀索引,
没必要对全字段建立索引,根据实际文本区分度决定索引长度即可。 使用前缀索引,对列的某几个字符进行索引,可以提高检索效率。
(7)合理创建联合索引,(a,b,c) 相当于 (a) 、(a,b) 、(a,b,c),区分度最高的列在最左边。
(8)合理使用覆盖索引减少IO,避免排序。
(9)不要过度使用索引,单个表上的索引数量建议不要超过5个 。
(10)删除不再使用或者很少使用的索引。
3.5 数据库里不建议存放业务日志
业务日志的写入量比较大,影响mysql的性能,建议存放到非关系型数据库中。
IV SQL设计规范
4.1 避免数据类型的隐式转换
例如:SQL中的字符串类型数据应该统一使用单引号。特别对纯数字的字符串,必须用单引号,否则会导致隐式转换而引起性能问题或索引失效问题。
4.2 避免复杂SQL
对于非常复杂的SQL,特别是有多层嵌套,带子句或相关子查询的,应该先考虑是否设计不当引起的。对于一些复杂SQL可以考虑使用程序实现。
4.3 批量插入
使用INSERT语句一定要给出插入值的字段列表,这样即使表加了字段也不会影响现有系统的运行。对于小批量插入,可以将多条记录合并为同一个SQL,使用INSERT INTO tablename (col1,col2,...) VALUES (value1, value2,...),(value1, value2,...),...; 插入多条数据只有一次提交,效率明显提高。对于大批量插入和文件的导入导出,避免使用insert .... select和create table…select的形式,可能会阻止对源表的并发更新,如果查询比较复杂,会造成严重的性能问题。推荐使用select...into outfile和load data infile的组合来实现,采用这种方式MySQL不会给source_tab 加锁,还可以大大缩短数据的导出导入时间。但是,由于这种方式存在一定的安全隐患,所以如果需要使用这种方式,必须提交DBA审批,审批通过以后才可执行。
4.4 数据更新
推荐使用主键更新,其它维度条件的更新操作会造成页锁。对多个表进行关联update操作风险较大,尤其是当执行计划出现错误时,可导致多个表同时被锁住,应该尽量避免。不带条件的update会导致全表操作,耗时较长,如有此需求,请联系DBA评估、操作。
4.5 避免使用TRUNCATE TABLE
TRUNCATE TABLE 比 DELETE速度快,且使用的系统和事务日志资源较少,也可以直接释放磁盘空间,但TRUNCATE无事务且不触发trigger,有可能造成事故,故不建议在代码中使用此语句。
TRUNCATE TABLE在功能上与不带where子句的delete语句相同。
4.6 避免使用SELECT *
如果不必要取出所有数据,不要用 * 来代替,应给出字段列表。
4.7 使用索引做条件查询count(*)
innodb引擎在统计方面和myisam是不同的,Myisam内置了一个计数器,所以在使用 select count(*) from table 的时候,直接可以从计数器中取出数据。而innodb必须全表扫描一次方能得到总的数量。每执行一次扫描一次,代价非常高。需要进行count(*)统计表记录总数时,加上secondary index扫描条件,可以加快扫描速度。例如:SELECT COUNT(*) FROM sbtest1 WHERE id>=0;
4.8 避免IN子句
使用 IN 或 NOT IN 子句时,特别是当子句中有多个值且表数据较多时,速度会明显下降。可以采用连接查询或外连接查询来提高性能。
4.9 避免不必要的排序
不必要的数据排序大大的降低系统性能。
比如:在使用group by col的时候,mysql会默认order by col ,在只需要分组不需要排序的情况下,可以使用GROUP BY col ORDER BY NULL提升执行效率,仅仅对col列分组,而不排序。
4.10 合理利用最左索引
组合索引的生效原则是:从前往后依次使用生效,如果中间某个索引没有使用,那么断点前面的索引部分起作用,断点后面的索引没有起作用。对于组合索引,注意索引的使用顺序,where子句中将最左索引放在第一列。
比如:(a,b,c) 三个列上加了联合索引(是联合索引,不是在每个列上单独加索引)where a=3 and b=45 and c=5 .... 这种三个索引顺序使用中间没有断点,全部发挥作用 where a=3 and c=5... 这种情况下b就是断点,a发挥了效果,c没有效果where b=3 and c=4... 这种情况下a就是断点,在a后面的索引都没有发挥作用,这种写法联合索引没有发挥任何效果where b=45 and a=3 and c=5 .... 这个跟第一个一样,全部发挥作用,abc只要用上了就行,跟写的顺序无关 。
4.11 多表连接
做多表操作时,应该给每个表取一个别名,每个表字段都应该标明其所属哪个表。
为关联操作的字段建立索引,并使用统一数据类型,不同数据类型做关联时,MySQL会进行隐式转换,导致无法用到索引,开销较大。
多表连接个数建议不超过3个。
4.12 避免在where后的索引字段上使用函数
在where后的索引字段上使用函数会导致索引失效,严重情况下会拖慢整个数据库实例的速度。
例如:
SELECT orderid
FROM order_detail
WHERE from_unixtime(create_time)>'2017-12-04 12:00:00';
这样使用函数会导致查询条件不使用索引,使查询性能下降。应改为:
SELECT orderid
FROM order_detail
WHERE create_time>unix_timestamp('2017-12-04 12:00:00');
4.13 尽量不要做’%’前缀模糊查询
col like “abc%” 能用上索引,而col like “%abc”不能用上索引
4.14 使用UNION ALL代替UNION
UNION合并两个或多个SELECT语句的结果集,并消去表中任何重复行。而UNION ALL不会消除重复行。从效率上说,UNION ALL要比UNION快很多,所以如果可以确认合并的多个结果集中不包含重复数据时,建议使用UNION ALL。
4.15 尽量避免OR操作
通常情况下,如果条件中有or,即使其中有条件带索引也不会使用,所以除非每个列都建立了索引,否则不建议使用OR。在多列OR中,建议用UNION ALL替换。
比如:
select f_crm_id from d_dbname1.t_tbname1 where f_xxx_id = 926067
and (f_mobile ='1234567891' or f_phone ='1234567891' );
应改为:
select f_crm_id from d_dbname1.t_tbname1 where f_xxx_id = 926067
and f_mobile ='1234567891'
UNION ALL
select f_crm_id from d_dbname1.t_tbname1 where f_xxx_id = 926067
and f_phone ='1234567891'
相同字段or可改成 in,如 f_id=1 or f_id=100 --> f_id in (1,100)。
4.16 MySQL 在否定条件中不能使用索引
例如,where 条件里面有<>、not in 、not exists的时候,即便是在这些判断字段上加有索引,也不会起作用。
4.7 MySQL 在JOIN中连接字段类型如果不一致,则不能使用索引
但是例外就是char和varchar如果在定义表的时候,长度一致,就可以利用索引JOIN,反正不行。例如,char(20)和varchar(20)可以利用索引,char(20)和varchar(25)则不行,不管varchar里面实际存储的值是多长。
4.18 如果两个字段列的字符集不同,不推荐JOIN
字符集不同的列,索引失效,容易引起慢查询故障。
V 完整性设计规范
采用数据库系统实现数据的完整性,这不但包括通过标准化实现的完整性而且还包括数据的功能性。
5.1 主键约束
每个表要求有主健,主健字段或组合字段必须满足非空属性和唯一性要求。
5.2 NULL值
(1)由于NULL值在参加任何运算时,结果均为NULL,所以尽可能把字段定义为NOT NULL。对于所有声明为NOT NULL的字段,必须显式指定默认值。
(2)不要使用count(列名)或者count(常量)来替代 count(*),count(*)是SQL92定义的标准统计行数的语法,跟数据库无关,跟null和非null无关。
说明:count(*)会统计值为null的行,而count(列名)不会统计此列为null的行。
(3)count(distinct col)计算该列除null之外不重复的行数
注意:count(distinct col1, col2),如果其中一列全为null,那么即使另一列有不同的值,也返回0。
(4)当某一列的值全为null,count(col)的返回结果为0,但sum(col)的返回结果为null,因此使用sum()时需要注意NPE问题。
例如,可以使用ISNULL()来判断是否为NULL值,来避免sum的NPE问题:
SELECT IF(ISNULL(SUM(g)), 0, SUM(g)) FROM table;
(5)NULL与任何值的直接比较都为null。
NULL<>NULL的返回结果是NULL,而不是false。
NULL=NULL的返回结果是NULL,而不是true。
NULL<>1的返回结果是NULL,而不是true。
5.3 视图使用原则
为了在应用程序和数据库之间提供一层抽象,可以为应用程序建立视图而不必直接访问表。使用试图可以简化操作,不用关注表结构的定义,可以把经常使用的数据集合定义成视图;屏蔽了表结构变化对用户的影响, 表增加列对视图没有影响,具有一定的独立性。此外,用户对视图不可以随意的更改和删除,可以保证数据的安全性。视图是虚拟的数据库表,在使用时要遵循以下原则:
A. 尽可能减少使用视图。
B. 视图中如果嵌套使用视图,级数不要超过3级。
C. 由于视图中只能固定条件或没有条件,所以对于数据量较大或随时间的推移逐渐增多的表,不宜使用视图。
D. 除特殊需要,避免类似SELECT * FROM [Table Name] 而没有检索条件的视图
E. 视图中尽量避免出现数据排序的SQL语句。
VI 安全性设计规范
6.1 数据库账号使用规范
严格管理程序的专用账号,禁止用户使用此账号进行数据操作。 请使用开发人员专用只读账号进行数据查询。
6.2 用户与权限
为不同用户设定允许的权限,管理和使用权限分离。确定每个用户对数据库表的操作权限,如查询、新增、更新等。每个用户拥有刚好能够完成任务的权限。
严格把控好管理权限,只将管理权限赋予管理员。禁止有super权限的应用程序账号存在。禁止有DDL、DCL权限的应用程序账号存在。
6.3 用户密码管理
用户帐号的密码必须进行加密处理,确保在任何地方查询都不会出现密码的明文。
VII 开发行为规范
7.1 总则
(1) 业务部门推广活动或上线新功能,必须提前通知DBA,并留出必要时间以便DBA完成压力评估和扩容 ;
(2) 单表多次alter操作必须合并一次操作;
例如:
要给表t增加一个字段aa,同时给已有的字段bb建立索引,通常的做法分为两步:
alter table t add column aa varchar(10);
然后增加索引:
alter table t add index idx_bb(bb);
正确的做法是:
alter table t add column aa varchar(10),add index idx_bb(bb);
(3) 怀疑有性能瓶颈的SQL及早提交DBA调优,避免上线出现性能问题;
(4) 批量更新数据,必须通知DBA进行审核,并在执行过程中观察服务及主从延迟;
(5) 重要业务库的变更,须告知DBA重要等级、是否数据备份和执行时间要求;
(6) 避免在业务高峰期批量更新、查询数据库;
(7) 提交线上建表改表需求,必须详细注明涉及到的所有SQL语句,便于DBA进行审核和优化;
(8) 所有DDL和DML语句必须要在运维平台上提交申请,禁止口头或通过聊天工具传送需求;
(9) 不要在MySQL数据库中存放业务逻辑,如果把业务逻辑放到数据库中,将会影响横向发展和上线测试。建议把业务逻辑提前,放到前端或中间逻辑层,数据库仅作为存储层,实现逻辑与存储的分离;
(10) 出现业务部门人为误操作导致数据丢失,需要恢复数据的,必须第一时间通知DBA,并提供准确时间地点、误操作语句等重要线索;
(11) 业务部门程序出现BUG等影响数据库服务的问题,必须及时通知DBA,便于维护服务稳定;
(12) 重要项目的数据库方案选型和设计必须提前通知DBA参与。
7.2 避免使用触发器
MySQL中触发器是行触发的,每次增加、修改或者删除记录都会触发进行处理,编写过于复杂的触发器或者增加过多的触发器对记录的插入、更新、删除操作会有比较严重的影响,因此不要将应用的处理逻辑过多地依赖于触发器来处理。触发器的功能通常可以用其他方式实现,确实需要采用触发器,请联系DBA进行确认。
7.3 避免使用存储过程和函数
在数据库服务器上进行大量的复杂运算会占用服务器的CPU,造成数据库服务器的压力,影响数据库的正常使用,所以应尽量将这些运算操作分摊到应用服务器上执行。此外,存储过程难以调试和扩展,数据库扩展能力远远不如应用。
7.4 避免使用视图
视图可能导致执行计划错乱,影响SQL运行效率。对视图的修改,数据库必须把它转化为对基本表的信息修改,不便于维护。
VIII 其他规范
8.1 编制文档
对所有的命名规范、限制、数据字典、存储过程、函数都要编制文档。数据库文档化会大大减少犯错的机会,对开发、支持和跟踪修改非常有用。
8.2 维护计划规范
(1) 数据归档设计
根据业务功能,做最小限度保留,将数据备份至归档库,系统功能兼容访问历史数据库。
(2) 数据归档删除
需要物理删除不需要归档的数据,直接由DBA排作业自动物理删除。