表设计
命名规范
1.1:命名形式
Object简称+_+功能模块简称+_+功能模块分功能简称
说明:
Object的简称如下:
表(Table):t
视图(View):v
存储程序(Procedure):p
函数(Function):f
功能模块的简称,eg:
移动端相关功能的表:mobile
功能模块分功能简称,eg:
移动端客户登陆信息表:customer
命名eg:t_mobile_customer
1.2:命名要求
1.2.1: 命名必须为小写的形式
MySQL有配置参数lower_case_table_names,不可动态更改,linux系统默认为0,即库表名以实际情况存储,大小写敏感。如果是1,以小写存储,大小写不敏感。如果是2,以实际情况存储,但以小写比较。大小写混拼很容易致使混乱。
1.2.2: 命名之间必须以“_”作为分隔符
1.2.3: 命名使用简称必须使用英语名词的简称
1.2.4: 命名必须不超过12个字符
库名、表名、字段名支持最多64个字符,但为了统一规范、易于辨识以及减少传输量,必须不超过12字符。
存储引擎
统一采用InnoDB存储引擎。
5.5以后的默认引擎,支持事务,行级锁,更好的恢复性,对高并发,多核,大内存,ssd等支持更好。对于我们现有的环境的备份,恢复更加合适。
当然,采用什么存储引擎根据业务的需求,就目前我们更适合InnoDB。对MyISAM,Memory暂无特别强烈的需求。
字段类型简介
1.1:有关×××类型的字段类型,取值范围,占用空间
类型 |
占用空间(字节) |
最小值(signed/Unsigned) |
最大值(signed/unsigned) |
TINYINT |
1 |
-128 0 |
127 255 |
SMALLINT |
2 |
-32768 0 |
32767 65535 |
MEDIUMINT |
3 |
-8388608 0 |
8388607 16777215 |
INT |
4 |
-2147483648 0 |
2147483647 4294967295 |
BIGINT |
8 |
-9223372036854775808 0 |
9223372036854775807 18446744073709551615 |
1.2:日期和时间类型
类型 |
占用空间(字节) |
取值范围(最小值/最大值) |
显示格式 |
DATETIME |
8 |
1000-01-01 00:00:00 9999-12-31 23:59:59 |
YYYY-MM-DD HH:MM:SS |
DATE |
3 |
1000-01-01 9999-12-31 |
YYYY-MM-DD |
TIMESTAMP |
4 |
1970-01-01 00:00:01UTC 2038-01-19 03:14:07UTC |
YYYY-MM-DD HH:MM:SS |
YEAR(4) |
1 |
1901 2155 |
YYYY |
YEAR(2) |
1 |
1970 2069 其中: 00~69代表2000~2069 70~99代表1970~1999 |
YY |
TIME |
3 |
-838:59:59 838:59:59 |
HH:MM:SS Or HHH:MM:SS |
说明:
有人奇怪为什么TIME类型的时间可以大于23.因为TIME类型不仅可以用来保存一天中的时间,也可以用来保存时间间隔,同时解释了为什么TIME类型也可以存在负值。
1.3:字符串类型
CHAR和VARCHAR是最常用的两种字符集类型。
一般来说:CHAR(N)用来保存固定长度的字符串,VARCHAR(N)用来保存变长字符类型。
CHAR类型,N的取值范围:0~255
VARCHAR类型,N的取值范围:0~65535
其中N都代表字符长度,而非字节长度。
1.4:BINARY和VARBINARY
BINARY和VARBINARY存储的是二进制的字符串,而非字符集型字符串。也就是说,BINARY和VARBINARY没有字符集的概念,对于排序和比较都是按照二进制值进行对比。
BINARY(N)和VARBINARY(N)中的N指的是字节长度,而非字符长度。
1.5:高精度类型
DECIMAL和NUMERIC类型在MySQL中被视为相同类型,用于保存必须为确切精度的值。对于如工资数据类型,当声明该类型的列,可以(并且通常必须)指定精度和标度,例如:salary DECIMAL(5,2)
在上述的例子中,5是精度,2是标度。精度表示保存值的主要位数,标度表示小数点后面可以保存的位数。
1.6:ENUM和SET类型
ENUM和SET类型都是集合类型,不同的是ENUM类型最多可枚举65536个元素,而SET类型最多枚举64个元素。
由于MySQL不支持传统的CHECK约束,因此通过ENUM和SET类型并结合SQL_MODE可以解决一部分问题。eg:对“性别”列的约束。
选择字段类型的原则
1.更小的通常更好
一般情况下,应该尽量使用可以正确存储的最小数据类型。更小的数据类型通常更快,因为它们占用更少的磁盘,内存和CPU缓存,并且处理时需要的CPU周期更少。
2.简单就好
简单数据类型的操作通常需要更少的CPU周期。eg:×××比字符操作代价更低。因为字符集和校对规则(排序规则)使字符比较比×××比较更复杂。这里有两个例子:一个是应该使用MySQL内建的类型(date,datetime,time)而不是字符串来存储日期和时间;另外一个,是应该用×××存储IP地址。
3.尽量避免NULL
通常情况下最好指定列为NOT NULL,除非真的需要存储NULL值。
如果查询中包含可以为NULL的列,对MySQL来说更难优化,因为可为NULL的列使得索引,索引统计和值比较都更复杂。可为NULL的列会使用更多的存储空间,在MySQl里也要特殊对待处理。当可为NULL的列被索引时,每个索引记录需要一个额外的字节,在MyISAM里甚至还可能导致固定大小的索引(eg:只有一个整数列的索引)变成可变大小的索引。如果计划在列上建索引,就应该尽量避免设计成可为NULL列。
列类型属性
字段类型的属性:UNSIGNED和ZEROFILL
1.1:UNSIGNED
UNSIGNED属性就是将数字类型无符号化。eg:INT的类型范围是-2147483648~2147483647
而INT UNSIGNED范围是0~4294967295
1.2: ZEROFILL
ZEROFILL属性非常有意思,更像是一个显示的属性。很多初学者往往对MySQL数据库中类型数字类型后面的长度值很迷茫。eg:定义字段a int(4) UNSIGNED ZEROFILL;假如向a字段插入数字1,select发现显示的结果为 a:0001,但是其实际存储的还是数字1。有关此部分的内容请参考:
http://lgdvsehome.blog.51cto.com/3360656/1243676
字段类型选择小建议
1.1: 建议使用UNSIGNED存储非负数值。
理由:同样的存储空间,更大的取值范围。
1.2: 存储精确浮点数必须使用DECIMAL替代FLOAT和DOUBLE。
a) mysql中的数值类型(不包括整型):
IEEE754浮点数: float (单精度) ,double 或 real (双精度)
定点数: decimal 或 numeric
单精度浮点数的有效数字二进制是24位,按十进制来说,是8位;双精度浮点数的有效数字二进制是53位,按十进制来说,是16 位。一个实数的有效数字超过8位,用单精度浮点数来表示的话,就会产生误差!同样,如果一个实数的有效数字超过16位,用双精度浮点数来表示,也会产生误差。
b) IEEE754标准的计算机浮点数,在内部是用二进制表示的,但在将一个十进制数转换为二进制浮点数时,也会造成误差,原因是不是所有的数都能转换成有限长度的二进制数。
即一个二进制可以准确转换成十进制,但一个带小数的十进制不一定能够准确地用二进制来表示。
eg:
mysql> create table t(value float(10,2));
Query OK, 0 rows affected (0.00 sec)
mysql> insert into t values(131072.67),(131072.68);
Query OK, 2 rows affected (0.00 sec)
Records: 2 Duplicates:0 Warnings: 0
mysql> select * from t\G;
*************************** 1. row ***************************
value: 131072.67
*************************** 2. row ***************************
value: 131072.69
2 rows in set (0.01 sec)
1.3:建议使用INT UNSIGNED存储IPV4。
使用INTUNSIGNED而不是char(15)来存储ipv4地址,通过MySQL函数inet_ntoa和inet_aton来进行转化;Ipv6地址目前没有转化函数,需要使用DECIMAL或者两个bigINT来存储。例如:
mysql> create table t ( ip INT UNSIGNED );
mysql>insert into t select INET_ATON('209.207.224.40');
mysql>select INET_NTOA(ip) from t;
INET_NTOA(ip):209.207.224.40
1.4:表字符集选择UTF8
a) 使用utf8字符集,如果是汉字,占3个字节,但ASCII码字符还是1个字节。
b) 统一,不会有转换产生乱码风险
c) 其他地区的用户(美国、印度、台湾)无需安装简体中文支持,就能正常看您的文字,并且不会出现乱码
d) ISO-8859-1编码(latin1)使用了单字节内的所有空间,在支持ISO-8859-1的系统中传输和存储其他任何编码的字节流都不会被抛弃。即把其他任何编码的字节流当作ISO-8859-1编码看待都没有问题,保存的是原封不动的字节流。
特别说明:就目前我们公司的情况已有环境为gbk,所以请选择gbk。
1.5:尽量避免使用TEXT和BLOB
仅仅当字符数量可能超过20000个的时候,可以使用TEXT类型来存放字符类数据。所有使用TEXT类型的字段必须和原表进行分拆,与原表主键单独组成另外一个表进行存放。
1.6:明确需求的精度
eg:所有需要精确到天的字段使用DATE,而非DATETIME或TIMESTAMP;
需要精确到秒,使用DATETIME或TIMESTAMP,注意两者的取值范围。
1.7:自增字段的类型只能选择INT或BIGINT,且明确标示为无符号(UNSIGNED),除非确实出现了负数。如果其值预估超过42亿才选择BIGINT。
1.8:字段名称统一使用小写,且注意避免使用系统保留的关键字。
索引
索引命名
1.1:非唯一索引命名,以idx_字段名_字段名_[字段名]的格式命名
1.2:唯一索引命名,以uniq_字段名_字段名_[字段名]的格式命名
1.3:索引名称必须为小写
索引建议
1.1: 索引中的字段数建议不超过5个
1.2: 单张表的索引数量控制在5个以内
1.3:独立的列:索引列不能是表达式的一部分,也不能是函数的参数
#我们经常会看到一些查询不当地使用索引,或者使得MySQL无法使用已有的索引。如果查询中的列不是独立的,则MySQL就不会使用索引。我们应该养成简化WHERE条件的习惯,始终将索引列单独放在比较符号一侧。
eg:一个常见的错误:
mysql>SELECT … WHERE TO_DAYS(CURRENT_DATE) – TO_DAYS(date_col) <= 10;
1.4:对于BLOB,TEXT或者很长的VARCHAR类型的列,必须使用前缀索引,因为MySQL不允许索引这些列的完整长度。
#使用前缀索引,要注意索引的选着性及MySQL无法使用前缀索引做ORDER BY和GROUP BY。
1.5:当出现服务器对多个索引做相交操作时(通常有多个AND条件),通常意味着需要一个包含所有相关列的多列索引,而不是多个独立的单列索引。
#通过EXPLAIN中Extra列,如果发现Using union(idx_1,idx_2),也就是所谓的索引合并时,要考虑使用多列索引。当WHERE条件后出现多个条件时,考虑使用多列索引,而不是为每个单独的列建立索引。
1.6:选择合适的索引列顺序。
#当不需要考虑排序和分组时,将选择性最高的列放在前面通常是很好的。
1.7:合理使用覆盖索引。
#设计优秀的索引应该考虑到整个查询,而不单单是WHERE条件部分。索引确实是一种查找数据的高效方式,但是MySQL也可以使用索引来直接获取列的数据,这样就不再需要读取数据行。如果一个索引包含(或者说覆盖)所有需要查询的字段的值,我们称之为“覆盖索引”。
eg:可以考虑在list_1,list_2上加一个多列索引,就可以使用这个索引做覆盖索引。
mysql>select list_1,list_2 from table_name;
1.8:避免多个范围条件
#对于范围条件查询,MySQL无法再使用范围列后面的其它索引列,但是对于“多个等值条件查询”则没有这个限制。
1.9:冗余和重复索引
#MySQL允许在相同列上创建多个索引,无论是有意的还是无意的。MySQL需要单独维护重复的索引,优化器在优化查询的时候也需要逐个地进行考虑,这会影响性能。重复索引是指在相同的列上按照相同的顺序创建的相同类型的索引。应该避免这样创建重复索引,发现以后也应该立即移除。eg:
CREATE TABLE test (
id INT NOT NULL PRIMIARY KEY,
a INT NOT NULL,
UNIQUE(id),
INDEX(id)
)ENGINE=innodb;
一个经验不足的用户可能是想创建一个主键,先加上唯一限制,然后再加上索引以供查询使用。事实上,MySQL的唯一限制和主键限制都是通过索引实现的,因此,上面的写法实际上在相同的列上创建了三个重复的索引。
冗余索引和重复索引有一些不同。如果创建了索引(A,B),再创建索引(A)就是冗余索引,因为这只是前一个索引的前缀索引。因此索引(A,B)也可以当作索引(A)来使用(这种冗余只是对B-Tree索引来说的)。但是如果再创建索引(B,A),则不是冗余索引,索引(B)也不是。另外其它类型的,如:哈希,全文索引,也不会是B-Tree索引的冗余索引。
冗余索引通常发生在为表添加新索引的时候。例如,有人可能会增加一个新的索引(A,B)而不是扩展已有的索引(A)。还有一种情况时将一个索引扩展为(A,ID),其中ID是主键,对于InnoDB来说主键列已经包含在二级索引中了,所以这也是冗余。
大多数情况下都不需要冗余索引,应该尽量扩展已有的索引而不是创建新索引。但也有时候处于性能方面的考虑需要冗余索引,因为扩展已有的索引会导致其变得太大,从而影响其它使用该索引的性能。
例如,如果在整数列上有一个索引,现在需要额外增加一个很长的VARCHAR列来扩展该索引,那性能可能会急剧下降。特别是有查询把这个索引当作覆盖索引,或者这是MyISAM表并且有很多范围查询的时候。
另外注意的是,表中的索引越多插入速度会越慢。
1.10:使用索引扫描来做排序
#要注意排序方向;索引的最左前缀的要求;同时注意索引列是否为范围查找。
eg:假设a,b,c创建了一个多列索引(D),下面这个查询使用不用的排序方向,但是索引列都是正序排序的,索引(D)不能在此用来做排序操作。
SELECT … WHERE a = ‘常量’ ORDER BYb DESC, c ASC;
eg:下面的查询不满足索引的最左前缀。
SELECT … WHERE a = ‘xxx’ ORDER BY c;
eg:下面这个查询在索引列上是范围条件,所以MySQL无法使用索引的其余列。
SELECT … WHERE a > ‘xxx’ ORDER BY b,c;