数据类型的选择的基本原则
- 更小的通常更好。更小的数据类型通常更快,因为它们占用更少的磁盘、内存和 CPU 缓存,并且处理时需要的 CPU 周期更少
- 简单就好。简单数据类型的操作通常需要更少的 CPU 周期
- 尽量避免 NULL。为 NULL 的列使得索引、索引统计和值比较都更为复杂,使得对于 MySQL 来说更难优化
数字类型
整数(whole number)类型
TINYINT、SMALLINT、MEDIUMINT、INT、BIGINT 分别使用8,16,24,32,64位存储空间。存储范围是 -2(N-1) 到 2(N-1)-1
整数类型有可选的 UNSIGNED 属性,表示不允许负值,这大致可以使整数的上限提升一倍。有符号和无符号使用相同的存储空间,并具有相同的性能。
MySQL 可以为整数类型指定宽度,例如 INT(11),对大多数应用这是没有意义的:它不会限制值的合法范围。这是规定了 MySQL 的一些交互工具用来显示字符的个数。对于存储和计算来说,INT(1) 和INT(20) 是相同的
实数(real number)类型
实数是带有小数部分的数字。DECIMAL、FLOAT(4字节)、DOUBLE(8字节)。其中DECIMAL支持精确计算,因为需要额外的空间和计算开销,所以应该尽量只在对小数进行精确计算时才使用 DECIMAL ,但在数据量大的时候,可以考虑使用 BIGINT 代替 DECIMAL ,只需要将小数放大相应的倍数即可。
字符串类型
字符串长度定义的不是字节数,而是字符数。
VARCHAR
因为一个字符在不同字符集对应的字节数不同,所以在不同字符集下,VARCHAR 类型能定义的长度是不一样的。
在 latin1(单字节字符集)下
非严格模式的话,可以创建表,但是会有一个警告,因为 MySQL 数据库自动将 VARCHAR 类型转换成了 TEXT 类型
经过测试发现,VARCHAR 类型的最大长度为 65532 。这是因为还有其他别的开销。
在 GBK 或 UTF-8 字符集下
不同字符集下对 max 的提示是不同的。因此 VARCHAR(N) 中的 N 指的是字符的长度。而文档中说明 VARCHAR 类型最大支持 65535 ,单位是字节,而且是表中所有 VARCHAR 列的长度总和,如果 VARCHAR 列的长度总和超过这个长度,依然无法创建
VARCHAR 类型用于存储可变长字符串,比定长类型更节省空间,因为它仅仅使用必要的空间。VARCHAR 需要使用 1 或 2 个额外字节记录字符串的长度:如果列的长度大于255,则需要1个字节,否则需要2个字节
对于 InnoDB 存储引擎,由于行是变长的。在 UPDATE 一个可变长字符串时,使得比原行变得更长,导致 InnoDB 存储引擎进行页分裂来使行可以放进页内
下面这些情况使用 VARCHAR 是合适的:字符串列的最大长度比平均长度大很多;列的更新很少;使用了像 UTF-8 这样复杂的字符集,每个字符都使用不同的字节数进行存储
CHAR
CHAR 类型是定长的,MySQL 总是根据定义的字符串长度分配足够的空间。当存储 CHAR 值时,MySQL 会删除所有的末尾空格。CHAR 适合存储很短的字符串或者所有值的接近同一个长度。
BLOB 和 TEXT 类型
BLOB 和 TEXT 都是为存储很大的数据而设计的字符串数据类型,分别采用二进制和字符串方式存储。实际上,它们分别属于两组不同的数据类型家族:字符串类型是TINYTEXT,SMALLTEXT,TEXT,MEDIUMTEXT,LONGTEXT;对应的二进制类型是TINYBLOB,SMALLBLOB,BLOB,MEDIUMBLOB,LONGBLOB。
MySQL 对 BLOB 和 TEXT 列进行排序与其他类型是不同的:它只对每个列的最前 max_sort_length 字节而不是整个字符串做排序。如果只需要排序前面一小部分字符,则可以减少 max_sort_length 的配置,或者使用 ORDER BY SUBSTRING(column,length)
使用枚举(ENUM)代替字符串类型
MySQL 在存储枚举时非常紧凑,会根据列表值的数量压缩到一个或者两个字节中。MySQL 在内部会将每个值在列表中位置保存为整数,并且在 .frm 文件中保存 "数字 - 字符串" 映射关系的查找表。使用如果数字作为枚举常量的话,这种双重性很容易造成混乱。还有就是枚举字段是按照内部存储的整数而不是定义的字符串进行排序的,第一种解决方式是:按照需要的顺序来定义枚举列。第二种解决方式是:在查询中使用 FIELD() 函数显式地指定排序顺序,但这会导致 MySQL 无法利用索引消除排序。
日期和时间类型
DATETIME 和 TIMESTAMP ,MySQL 能存储最小时间粒度为秒。但是 MySQL 能使用微秒级的粒度进行临时运算。
DATETIME
能保存从 1001 年 到 9999 年,精度为秒,与时间无关。使用 8 个字节的存储空间。
TIMESTAMP
TIMESTAMP 类型保存了从 1970 年 1 月 1 日午夜(格林尼治标准时间)以来的秒数。使用 4 个字节的存储空间,因此它的范围比 DATETIME 小得多,只能从 1970 年到 2038 年。
MySQL 提供了函数 FROM_UNIXTIME() 把 Unix 时间戳转为日期,并提供了 UNIX_TIMESTAMP() 函数把日期转换为 Unix 时间戳。
如果要存储比秒更小粒度的日期和时间值,可以使用 BIGINT 类型存储微秒级别的时间戳或者使用 DOUBLE 存储秒之后的小数部分。
位数据类型
BIT
MySQL 把 BIT 当作字符串类型,而不是数字类型。当检索 BIT(1) 值时,结果是一个包含二进制 0 或 1 值的字符串,而不是ASCII 码的 "0" 或 "1",然而,在数字上下文的场景中检索时,结果是将位字符串转化成数字。这是相对让人费解的,所以应该谨慎使用 BIT 类型
SET
如果想要保存很多 true/false 值,可以考虑合并这些列到一个 SET 数据类型,它在 MySQL 内部是以一系列打包的位的集合来表示的。这样就有效地利用了存储空间。它的主要缺点是改变列的定义的代价较高:需要ALTER TABLE,这对大表来说是非常昂贵的操作。(需要获取表的排它锁)
选择标识符
为标识列选择适合的数据类型非常重要。一般更有可能用标识列与其他值进行比较,或者通过标识列寻找其他列。标识列也可能在另外表中作为外键使用。所以为标识列选择数据类型时,应该选择跟关联表中对应列一样的类型
- 整数类型
整数通常是标识列最好的选择,因为他们很快并且使用 AUTO_INCREMENT - ENUM 和 SET 类型
对于标识列来说,ENUM 和 SET 类型通常是一个糟糕的选择,尽管对某些只包含固定状态或者类型的静态"定义表"来说可能是没有问题的。ENUM 和 SET 列适合存储固定信息 - 字符串类型
如果可能,避免使用字符串类型作为标识符,因为它们很消耗空间,并且通常比数字类型慢
对于完全随机的字符串也需要多加注意,例如 MD5()、SHA1() 或者 UUID() 产生的字符串。这些函数生成的新值会任意分布在很大的空间内,这会导致 INSERT 以及一些 SELECT 语句变得很慢。
如果存储 UUID 值,则应该移除 "-" 符号;或者更好的做法是,用 UNDEX() 函数转换 UUID 值为 16 字节的数字,并且存储在一个 BINARY(16) 列中,检索时可以通过 HEX() 函数来格式化为 十六进制格式。