规范 - 数据库
一、表设计
1、库名、表名、字段名****必须使用小写字母,“_”分割。
a) MySQL 有配置参数 lower_case_table_names,不可动态更改,linux 系统默认为 0,即库表名以实际情况存储,大小写敏感。如果是 1,以小写存储,大小写不敏感。如果是 2,以实际情况存储,但以小写比较。
b) 如果大小写混合用,可能存在 abc,Abc,ABC 等多个表共存,容易导致混乱。
c) 字段名显示区分大小写,但实际使用不区分,即不可以建立两个名字一样但大小写不一样的字段。
d) 为了统一规范, 库名、表名、字段名使用小写字母。
2、库名、表名、字段名必须不超过 12 个字符。
库名、表名、字段名支持最多 64 个字符,但为了统一规范、易于辨识以及减少传输量,必须不超过
12字符。
**3、**库名、表名、字段名见名知意,建议使用名词而不是动词。
a) 例如用户评论可用表名 usercomment 或者 comment。
b) 库表是一种客观存在的事物,一种对象,所以建议使用名词。
**4、**必须使用 InnoDB 存储引擎。
a) 5.5 以后的默认引擘,支持事务,行级锁,更好的恢复性,高并发下性能更好,对多核,大内存, ssd 等硬件支持更好。
b) 具体比较可见附件的官方白皮书。
5**、存储精确浮点数****必须使用 DECIMAL 替代 FLOAT 和 DOUBLE**。
a) mysql 中的数值类型(不包括整型):
浮点数: float (单精度) , double 或 real (双精度)
定点数: decimal 或 numeric
单精度浮点数的有效数字二进制是 24 位,按十进制来说,是 8 位;双精度浮点数的有效数字二进制是 53 位,按十进制来说,是 16 位
一个实数的有效数字超过 8 位,用单精度浮点数来表示的话,就会产生误差!同样,如果一个实数的有效数字超过 16 位,用双精度浮点数来表示,也会产生误差
b) 标准的计算机浮点数,在内部是用二进制表示的,但在将一个十进制数转换为二进制浮点数时,也会造成误差,原因是不是所有的数都能转换成有限长度的二进制数。
即一个二进制可以准确转换成十进制,但一个带小数的十进制不一定能够准确地用二进制来表示。
实例:
drop table if exists t;
create table t(value float(10,2));
insert into t values(131072.67),(131072.68);
select value from t;
+-----------+
| value |
+-----------+
| 131072.67 |
| 131072.69 |
6**、建议使用 UNSIGNED 存储非负数值。**
同样的字节数,存储的数值范围更大。如 smallint 有符号为–32768 -32767,无符号为 0 - 65535
7**、** 如何使用INT UNSIGNED存储ip?
使用INT UNSIGNED而不是char(15)来存储ipv4地址,通过MySQL函数inet_ntoa和inet_aton来进行转化。Ipv6地址目前没有转化函数,需要使用DECIMAL或者两个bigINT来存储。例如:
SELECT INET_ATON('209.207.224.40');
3520061480
SELECT INET_NTOA(3520061480);
209.207.224.40
8**、整形定义中不添加长度,比如使用 INT,而不是 INT(4)。**
注意数值类型括号后面的数字只是表示宽度而跟存储范围没有关系,比如 INT(3)默认显示 3 位,空格补齐,超出时正常显示,python、java 客户端等不具备这个功能。
9**、使用短数据类型,比如取值范围为 0-80 时,使用 SMALLINT UNSIGNED。**
10**、不建议使用 ENUM、SET 类型和TINYINT,使用 SMALLINT来代替。**
a) ENUM,有三个问题:添加新的值要做 DDL,默认值问题(将一个非法值插入 ENUM(也就是说,允许的值列之外的字符串),将插入空字符串以作为特殊错误值),索引值问题(插入数字实际是插入索引对应的值)
b) 由于mysql没有boolean类型,所以会用到TINYINT[1]类型来表示,在mysql中
boolean=tinyint[1],在别的数据库语言中也会被解读成是true/false,为了避免不必要的麻烦,在我们系
统中一律不使用tinyint,使用smallint来代替
(详解可以参阅mysql的tinyint(1).docx)
实例:
drop table if exists t;
create table t(sex enum('0','1'));
insert into t values(1);
insert into t values('3');
select * from t;
+------+
| sex |
+------+
| 0 |
| |
+------+
2 rows in set (0.00 sec)
11**、尽可能不使用 TEXT、BLOB 类型。**
a) 索引排序问题,只能使用 max_sort_length 的长度或者手工指定 ORDER BY SUBSTRING(column, length)的长度来排序
b) Memory 引擘不支持 text,blob 类型,会在磁盘上生成临时表
c) 可能浪费更多的空间
d) 可能无法使用 adaptive hash index
e) 导致使用 where 没有索引的语句变慢
12**、VARCHAR(N),N 表示的是字符数不是字节数,比如 VARCHAR(255),可以最大可存**
储255 个汉字,需要根据实际的宽度来选择 N。
13、VARCHAR(N),N 尽可能小,因为 MySQL 一个表中所有的 VARCHAR 字段最大长度是 65535 个字节,进行排序和创建临时表一类的内存操作时,会使用 N 的长度申请内存。
VARCHAR 中会产生额外存储吗?
VARCHAR(M),如果 M<256 时会使用一个字节来存储长度,如果 M>=256 则使用两个字节来存储
长度。
14**、 如果表里有字段需要存储表情使用表字符集选择 Utf8mb4**
a) Utf8mb4兼容utf8,且比utf8能表示更多的字符,例 存储emoji表情,utf8是无法存储的,
Utf8mb4则可以做到
b) 统一,不会有转换产生乱码风险
c) 其他地区的用户(美国、印度、台湾)无需安装简体中文支持,就能正常看您的文字,并且不会出现乱码
d) mysql 5.5.3版本以后支持Utf8mb4
15**、存储年使用 YEAR 类型。**
16**、存储日期使用 DATE 类型。**
17**、建议字段设置不允空。**
18**、建议字段默认值不要为NUll。**
a)如果 null 字段被索引,需要额外的 1 字节
b)使索引,索引统计,值的比较变得更复杂
c)可用 0,''代替
d) 如果是索引字段,一定要定义为 not null
19**、所有表、字段均应用 comment 列属性来描述此表、字段所代表的真正含义,如枚举值则建议将该字段中使用的内容都定义出来。**
20**、如无说明,表中的第一个id字段一定是主键 bigint unsigned** 且为自动增长,禁止在非事务内上下文作为条件进行数据传递。禁止使用varchar类型作为主键语句设计。
21**、如无说明,表必须包含create_time和modify_time字段**
create_time TIMESTAMP(3) not null DEFAULT CURRENT_TIMESTAMP(3);
modify_time TIMESTAMP(3) not null DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3);
create_time建立索引
modify_time建立索引
22**、如无说明,表必须包含is_del,用来标示数据是否被删除,原则上数据库数据不允许物理删除。**
is_del tinyint not null default 0 comment'是否删除:0否,1是'
23**、禁止在数据库中使用** VARBINARY**、BLOB** 存储图片、文件等。
采用分布式文件系统更高效
24**、表结构变更需要通知 DBA 审核。**
二、索引
1**、 为什么 MySQL 的性能依赖于索引?**
MySQL 的查询速度依赖良好的索引设计,因此索引对于高性能至关重要。合理的索引会加快查询速度(包括UPDATE和DELETE的速度,MySQL会将包含该行的page加载到内存中,然后进行UPDATE或者 DELETE 操作),不合理的索引会降低速度。
MySQL 索引查找类似于新华字典的拼音和部首查找,当拼音和部首索引不存在时,只能通过一页一页的 翻页来查找。当 MySQL 查询不能使用索引时,MySQL 会进行全表扫描,会消耗大量的 IO
2**、非唯一索引必须按照“idx_字段名称_字段名称[_字段名]”进行命名。**
3**、唯一索引须按照“uniq_字段名称_字段名称[_字段名]”必进行命名。**
4**、索引名称必须使用小写。**
5**、索引中的字段数建议不超过 5 个。**
InnoDB 的 secondary index 使用 b+tree 来存储,因此在 UPDATE、DELETE、INSERT 的时候需要对 b+tree 进行调整,过多的索引会减慢更新的速度。
6**、单张表的索引数量控制在 5 个以内**。
7**、唯一键由 3 个以下字段组成,并且字段都是整形时,使用唯一键作为主键。**
8**、没有唯一键或者唯一键不符合 5 中的条件时,使用自增(或者通过发号器获取)id 作为**
主键。
9**、唯一键不和主键重复**。
10**、索引字段的顺序需要考虑字段值去重之后的个数,个数多的放在前面(复合索引的最左原则)。**
11**、ORDER BY,GROUP BY,DISTINCT 的字段需要添加在索引的后面。**
12**、使用 EXPLAIN 判断 SQL 语句是否合理使用索引,尽量避免 extra 列出现:Using File Sort,Using Temporary。**
EXPLAIN 语句(在 MySQL 客户端中执行)可以获得 MySQL 如何执行 SELECT 语句的信息。通过对 SELECT 语句执行 EXPLAIN,可以知晓 MySQL 执 行该 SELECT 语句时是否使用了索引、全表扫描、临时表、排序等信息。尽量避免 MySQL 进行全表扫描、使用临时表、排序等。详见官方文档。
13**、UPDATE、DELETE 语句需要根据 WHERE 条件添加索引。**
14**、不建议使用%前缀模糊查询,例如 LIKE “%weibo”。**
会导致全表扫描
15****合理创建联合索引(避免冗余),(a,b,c) 相当于 (a) 、(a,b) 、(a,b,c)。
从左到右的使用索引中的字段,一个查询可以只使用索引中的一部份,但只能是最左侧部分
索引是key index (a,b,c). 可以支持a | a,b| a,b,c 3种组合进行查找,但不支持 b,c进行查找 .当最左侧字段是常量引用时,索引就十分有效
**16 **经常join其他表,在连接字段上应该建立索引 经常出现在Where子句中的字段,特别是大表的字段,应该建立索引 复合索引的几个字段是否经常同时以AND方式出现在Where子句中,单字段查询是否极少甚至没有,如果是,则可以建立复合索引;否则考虑单字段索引
17**、SQL 变更需要确认索引是否需要变更并通知 DBA。**
三、 SQL 语句
1**、使用 prepared statement,可以提供性能并且避免 SQL 注入。**
2**、SQL 语句中 IN 包含的值不应过多。**
3**、UPDATE语句不使用 LIMIT**。
a) 可能导致主从数据不一致
b) 会记录到错误日志,导致日志占用大量空间
4**、WHERE 条件中必须使用合适的类型,避免 MySQL 进行隐式类型转化。**
因为 MySQL 进行隐式类型转化之后,可能会将索引字段类型转化成=号右边值的类型,导致使用不
到索引,原因和避免在索引字段中使用函数是类似的。(字符型应该加引号,数值型不加引号)
例 select * from user where mobile=159000000,mobile字段类型是字符型,值却给了数值型
5**、SELECT、INSERT 语句必须显式的指明字段名称,不使用 SELECT 。*
增加很多不必要的消耗(cpu、io、内存、网络带宽);增加了使用覆盖索引的可能性;当表结构发
生改变时,前段也需要更新。
6**、WHERE 条件中的非等值条件(IN、BETWEEN、<、<=、>、>=)会导致后面的条件使用不了索引。**
7**、避免在 SQL 语句进行数学运算或者函数运算,容易将业务逻辑和 DB 耦合在一起。**
8**、INSERT 语句使用 batch 提交(INSERT INTO table VALUES(),(),()……),values 的个数不应过多**。
9**、多表连接时,尽量小表驱动大表,即小表 left join 大表。**
10**、使用合理的 SQL 语句减少与数据库的交互次数。**
使用下面的语句来减少和 db 的交互次数:
INSERT ... ON DUPLICATE KEY UPDATE
REPLACE INTO
INSERT IGNORE
INSERT INTO VALUES(),(),()
UPDATE … WHERE ID IN(10,20,50,…)
11**、不使用 ORDER BY RAND(),使用其他方法替换。**
因为 ORDER BY rand()会将数据从磁盘中读取,进行排序,会消耗大量的 IO 和 CPU,可以在程序
中获取一个 rand 值,然后通过在从数据库中获取对应的值
12**、 InnoDB 存储引擎为什么避免使用 COUNT(*)?**
InnoDB 表避免使用 COUNT(*)操作,计数统计实时要求较强可以使用 memcache 或者 redis,非实时统计可以使用
**13 **不使用负向查询,如not in/like 无法使用索引,导致全表扫描 全表扫描导致buffer pool利用率降低
14**、使用EXPLAIN诊断,避免生成临时表**
EXPLAIN****语句(在MySQL客户端中执行)可以获得MySQL如何执行SELECT语句的信息。通过对SELECT语句执行EXPLAIN,可以知晓MySQL执行该SELECT语句时是否使用了索引、全表扫描、临时表、排序等信息。尽量避免MySQL进行全表扫描、使用临时表、排序等。详见官方文档。
15**、禁止单条SQL语句同时更新多个表**
四、分表
1、每张表数据量建议控制在 2000w 以下。
2、推荐使用 求余(或者类似的算术算法)进行分表,表名后缀使用数字,数字必须从 0 开始并等宽,比如分 100 张表,后缀从 00-99。
3、使用时间分表,表名后缀必须使用特定格式,比如按日散表 user_20110209、按月分表
user_201102。
五、其他
1、批量导入、导出数据需要 DBA 进行审查,并在执行过程中观察服务。
2、批量更新数据,如 update,delete 操作,需要 DBA 进行审查,并在执行过程中观察服
务。
3、产品出现非数据库平台运维导致的问题和故障时,如前端被抓站,请及时通知 DBA,便于维护服务稳定。
4、业务部门程序出现 bug 等影响数据库服务的问题,请及时通知 DBA,便于维护服务稳定。
5、业务部门推广活动,请提前通知 DBA 进行服务和访问评估。
6、如果出现业务部门人为误操作导致数据丢失,需要恢复数据,请在第一时间通知 DBA,并提供准确时间,误操作语句等重要线索。
7、SQL 变更需要确认索引是否需要变更并通知 DBA
8、表结构变更需要通知 DBA 审核