良好的逻辑设计和物理设计是高性能的基础,应该根据系统将要执行的查询语句来设计schema,这往往需要权衡各种因素。
选择优化的数据类型
Mysql支持的数据类型非常多,选择正确的数据类型对于获得高性能至关重要。不管存储哪种类型的数据,下面几个简单的原则都有助于做出更好的选择。
- 更小的通常更好
 
一般情况下,应该尽量使用可以正确存储数据的最小数据类型。
- 简单就好
 
简单数据类型的操作通常需要更少的CPU周期。
- 尽量避免NULL
 
很多表都可包含为NULL(空值)的列,即使应用程序并不需要保存NULL也是如此,这是因为可为NULL是列的默认属性。通常情况下最好指定列为NOT NULL,除非真的需要存储NULL值。
在为列选择数据类型时,第一步需要确定合适的大类型:数字、字符串、时间等。
下一步是选择具体类型。很多Mysql的数据类型可以存储相同类型的数据,只是存储的长度和范围不一样、允许的精度不同,或者需要的物理空间(磁盘和内存空间)不同。相同大类型的不同子类型数据有时也有一些特殊的行为和属性。
整数类型
有两种类型的数字:整数和实数。如果存储整数,可以使用这几种整数类型:TINYINT,SMALLINT,MEDIUMINT,INT,BIGINT。分别使用8,16,24,32,64位存储空间。它们可以存储的值得范围从到,其中N是存储空间的位数。
整数类型有可选的UNSIGNED属性,表示不允许空值,这大致可以使正数的上限提高一倍。
实数类型
实数是带有小数部分的数字。然而,它们不只是为了存储小数部分;也可以使用DECIMAL存储比BIGINT还大的整数。Mysql既支持精确类型,也支持不精确类型。
FLOAT和DOUBLE类型支持使用标准的浮点运算进行近似计算。DECIMAL类型用于存储精确地小数。浮点和DECIMAL类型都可以指定精度。
浮点类型在存储同样范围的值时,通常比DECIMAL使用更少的空间。FLOAT使用4个字节存储。DOUBLE占用8个字节。
字符串类型
VARCHAR和CHAR类型
VARCHAR和CHAR是两种最主要的字符串类型。
关于两种类型的一些比较。
- VARCHAR
 
VARCHAR类型用于存储可变长字符串,是最常见的字符串数据类型。它比定长类型更节省空间,因为它仅使用必要的空间。
**VARCHAR需要使用1或2个额外字节记录字符串的长度:如果列的最大长度小于或等于255字节,则只使用1个字节表示,否则使用2个字节。**假设使用latin1字符集,一个VARCHAR(10)的列需要11个字节的存储空间。VARCHAR(1000)的列则需要1002个字节,因为需要2个字节存储长度信息。
下面这些情况下使用VARCHAR是合适的:字符串列的最大长度比平均长度大很多;列的更新更少,所以碎片不是问题;使用了像UTF-8这样复杂的字符集,每个字符都使用不同的字节数进行存储。
- CHAR
 
CHAR类型是定长的:Mysql总是根据定义的字符串长度分配足够的空间。当存储CHAR值时,Mysql会删除所有的末尾空格。CHAR值会根据需要采用空格进行填充以方便比较。
**CHAR适合存储很短的字符串,或者所有值都接近同一个长度。对于经常变更的数据,CHAR也比VARCHAR更好,因为定长的CHAR类型不容易产生碎片。对于非常短的列,CHAR比VARCHAR在存储空间上也更有效率。**例如用CHAR(1)来存储只有Y和N的值,如果采用单字节字符集只需要一个字节,但是VARCHAR(1)却需要两个字节,因为还有一个记录长度的额外字节。
与CHAR和VARCHAR类似的类型还有BINARY和VARBINARY,它们存储的是二进制字符串。二进制字符串跟常规字符串非常相似,但是二进制字符串存储的是字节码而不是字符。填充也不一样:Mysql填充BINARY采用的是\0(零字节)而不是空格,在检索时也不会去掉填充值。
使用
VARCHAR(5)和VARCHAR(200)存储‘hello’的空间开销是一样的。那么使用更短的列有什么优势吗?
事实证明有很大优势。更长的列会消耗更多的内存,因为Mysql通常会分配固定大小的内存块来保存内存值。尤其是使用内存临时表进行排序或操作时会特别糟糕。在利用磁盘临时表进行排序时也同样糟糕。所以最好的策略是只分配真正需要的空间。
BLOB和TEXT类型
BLOB和TEXT都是为存储很大的数据而设计的字符串数据类型,分别采用二进制和字符方式存储。
字符类型是TINYTEXT,SMALLTEXT,TEXT,MEDIUMTEXT,LONGTEXT;对应的二进制类型是TINYBLOB,SMALLBLOB,BLOB,MEDIUMBLOB,LONGBLOB。BLOB是SMALLBLOB的同义词,TEXT是SMALLTEXT的同义词。
与其他类型不同,Mysql把每个BLOB和TEXT值当作一个独立的对象处理。存储引擎在存储时通常会做特殊处理。当BLOB和TEXT值太大时,InnoDB会使用专门的外部存储区域来进行存储,此时每个值在行内需要1~4个字节存储指针,然后在外部存储区域存储实际值。
BLOB和TEXT家族之间仅有的不同是BLOB类型存储的是二进制数据,没有排序规则或字符集,而TEXT字符有字符集和排序规则。
Mysql不能将BLOB和TEXT列全部长度的字符串进行索引,也不能使用这些索引消除排序。
使用枚举(ENUM)代替字符串类型
有时候可以使用枚举列代替常用的字符串类型。枚举列可以把一些不重复的字符串存储成一个预定义的集合。
日期和时间类型
Mysql可以使用许多类型来保存日期和时间值,例如YEAR和DATE。Mysql能存储的最小时间粒度为秒。但是Mysql也可以使用微秒级的粒度进行临时运算。
DATETIME
这个类型能保存大范围的值,从1001年到9999年,精度为秒。它把日期和时间封装到格式为YYYYMMDDHHMMSS的整数中,与时区无关。使用8个字节的存储空间。
默认情况下,Mysql以一种可排序的、无歧义的格式显示DATETIME值,例如“2008-01-16 22:37:08”。这是ANSI标准定义的日期和时间表示方法。
TIMESTAMP
TIMESTAMP类型保存了从1970年1月1日午夜(格林尼治标准时间)以来的秒数,它和UNIX时间戳相同。TIMESTAMP只使用4个字节的存储空间,因此它的范围比DATETIME小得多:只能表示从1970年到2038年。Mysql提供了FROM_UNIXTIME()函数把Unix时间戳转换为日期,并提供了UNIX_TIMESTAMP()函数把日期转换为Unix时间戳。
TIMESTAMP显示的值依赖于时区。Mysql服务器、操作系统,以及客户端连接都有时区设置。
TIMESTAMP有DATETIME没有的特殊属性。默认情况下,如果插入时没有指定第一个TIMESTAMP列的值,Mysql则设置这个列的值为当前时间。再更新一条记录时,Mysql默认也会更新第一个TIMESTAMP列的值(除非在UPDATE语句中明确指定了值)。TIMESTAMP列默认为NOT NULL,这也和其他的数据类型不一样。
除了特殊行为外,通常也应该尽量使用TIMESTAMP,因为它比DATETIME空间效率更高。
位数据类型
Mysql有少数几种存储类型使用紧凑的位存储数据。所有这些位类型,从技术上来说都是字符串类型。
BIT
可以使用BIT列在一列中存储一个或多个``true/false值。BIT(1)定义一个包含单个位的字段,BIT(2)存储两个位,依此类推。BIT`列的最大长度是64个位。
应该谨慎使用BIT类型。对于大部分应用,最好避免使用这种类型。
如果想在一个bit的存储空间中存储一个true/false值,另一个方法是创建一个可以为空的CHAR(0)列。该列可以保存空值(NULL)或者长度为零的字符串(空字符串)。
SET
如果需要保存很多true/false值,可以考虑合并这些列到一个SET数据类型,它在Mysql内部是以一系列打包的位的集合来表示的。
选择标识符
- 整数类型
 
整数通常是标识列最好的选择,因为它们很快并且可以使用AUTO_INCREMENT。
- ENUM和SET类型
 
对于标识列来说,ENUM和SET类型通常是一个糟糕的选择。
- 字符串类型
 
如果可能,应该避免使用字符串类型作为标识列,因为它们很消耗空间,并且通常比数字类型慢。
如果存储UUID值,则应该移除“-”符号,或者更好的做法是,用UNHEX()函数转换UUID值为16字节的数字,并且存储在一个BINARY(16)列中。检索时可以通过HEX()函数来格式化为十六进制格式。
Mysql schema设计中的陷阱
- 太多的列
 - 太多的关联(一个粗略的经验法则,如果希望执行得快速其并发性好,单个查询最好在12个表以内做关联)
 - 全能的枚举(注意防止过度使用枚举)
 - 变相的枚举
 - 非此发明的
NULl(当确实需要表示未知值时也不要害怕使用NULL) 
总结
- 尽量避免过度设计
 - 使用小而简单的合适数据类型
 - 尽量使用相同的数据类型存储相似或相关的值
 - 注意可变长字符串,其在临时表和排序时可能导致悲观的按最大长度分配内存
 - 尽量使用整型定义标识列
 - 避免使用Mysql已经遗弃的特性
 - 小心使用
ENUM和SET - 范式是好的,但是反范式有时也是必需的,并且能带来好处
 ALTER TABLE是让人痛苦的操作,因为在大部分情况下,它都会锁表并且重建整张表

