一、字段优化的基本原则
- 更小更简单的字段类型更好
更小的数据类型通常更快,因为重用磁盘、内存和CPU缓存会更少,处理是需要使用到的时钟周期也会更少,而简单数据类型的操作通常需要更少的CPU周期。
如果一个类型既可以用字符串又能用整型,优先选择整型,因为字符集和校对规则(排序规则)使字符比较比整型比较更复杂等,还有应该使用MySQL内建的类型而不是字符串来存储日期和时间。所以,尽量使用可以正确存储数据的最小的数据类型和M有SQL内建类型。
- 尽量避免NULL
除非针对需要NULL值,不然列都指定为NOT NULL。
NULL值对于MySQL的查询来说更难优化,因为NULL的列使得索引(单列索引不存储null值,复合索引不存储全为null的值。所以对这列采用is null条件时,因为索引上根本没Null值,不能利用到索引,只能全表扫描)、索引统计和值的比较更复杂(Null值的特殊性就在于参与的运算大多取值为null),而且当可为NULL的列被索引时,每个索引记录需要一个额外的字节。
不过把可为NULL的列改为NOT NULL带来的性能提升比较小。对于distinct和group by来说,所有的NULL值都会被视为相等,对于order by来说升序NULL会排在最前
ORDER BY 把NULL放前面:
GROUP BY 和 DISTINCT 把id为4、5且passenger_phone为NULL的当作相同的行
二、整数类型
- 整数类型:TINYINT,SMALLINT,MEDIUMINT,INT,BIGINT。分别占用1、2、3、4、8个字节。
对于整型有UNSIGNED属性,表示不允许负数,可以使得正数的存储上限提高一倍。例如TINYINT UNSIGNED可以存储的范围是0 ~ 255,而TINYINT的存储范围是-128 ~ 127。不过有符号和无符号类型使用相同的存储空间,并具有相同的性能,看具体业务要求。
- 注意MySQL中的bigint对应Java中的Long类型,都是占8字节。
三、实数类型
- 什么是实数?实数是带有小数部分的数字。
- MySQL既支持精确类型的存储DECIMAL类型,也支持不精确类型存储FLOAT和 DOUBLE类型(浮点类型)。DECIMAL类型用于存储精确的小数,本质上MySQL是以字符串形式存放的。所以CPU不支持对DECIMAL的直接计算,只是在MySQL中自身实现了DECIMAL的高精度计算。相对而言,CPU直接支持原生浮点计算,所以浮点运算明显更快。
- 精确类型和非精确类型怎么选?
在精度不敏感和需要快速运算的时候,选择FLOAT和 DOUBLE;
尽量只在对小数进行精确计算时才使用DECIMAL;
遇到数据量比较大的而且要求精度时,可以考虑使用BIGINT代替DECIMAL,将需要存储的货币单位根据小数的位数乘以相应的倍数即可。如需要存储数据需精确到小数点后5位时,可以把数据与十万相乘,存入BIGINT中,可以同时避免浮点存储计算不精确和 DECIMAL精确计算代价高的问题。
四、字符串类型
MySQL支持多种字符串类型,包括VARCHAR和CHAR类型、BLOB和TEXT类型、ENUM(枚举)和SET类型。
VARCHAR类型
- VARCHAR类型用于存储可变长字符串,是最常见的字符串数据类型。它比定长类型更节省空间,因为它仅使用必要的空间(例如:越短的字符串使用越少的空间)。在内部实现上,既然是变长,VARCHAR需要使用1或2个额外字节记录字符串的长度,如果列的最大长度小于或等于255字节,则只使用1个字节表示,否则使用2个字节。
- VARCHAR节省了存储空间,所以对性能也有帮助。但是,由于行是变长的,在UPDATE时新值比旧值长时,使行变得比原来更长,这就肯定导致需要做额外的工作。如果一个行占用的空间增长,并且在页内没有更多的空间可以存储,在这种情况下,MyISAM会将行拆成不同的片段存储,InnoDB则需要分裂页来使行可以放进页内。
注意:在 InnoDB 存储引擎中,所有的数据都被逻辑地存放在表空间中,表空间(tablespace)是存储引擎中最高的存储逻辑单位,在表空间的下面又包括段(segment)、区(extent)、页(page),他们之间的关系如下:
页是MySQL中磁盘和内存交换的基本单位,也是MySQL管理存储空间的基本单位。同一个数据库实例的所有表空间都有相同的页大小。默认情况下,表空间中的页大小都为16KB,当然也可以通过改变 innodb_page_size 选项对默认大小进行修改,需要注意的是不同的页大小最终也会导致区大小的不同。
一次最少从磁盘读取16KB内容到内存中,一次最少把内存中16KB内容刷新到磁盘中,当然了单页读取代价也是蛮高的,一般都会进行预读。
CHAR类型
- CHAR类型是定长的,MySQL总是根据定义的字符串长度分配足够的空间。当存储时,MySQL会删除所有CHAR值的末尾空格,比较时,CHAR值会根据需要采用空格进行填充。
- CHAR和VARCHAR如何选择?
对于VARCHAR:字符串列的最大长度比平均长度大很多;列的更新很少;每个字符进行存储使用字节数差异比较大,不固定。
对于CHAR:适合存储很短的字符串,或者所有值定长或都接近同一个长度。例如只有一个值(是否)如果采用单字节CHAR(1)字符集只需要一个字节,但是VARCHAR(1)却需要两个字节,因为还有一个记录长度的额外字节。而且定长的CHAR类型不容易产生碎片。
注意:使用VARCHAR(5)和VARCHAR(255)存储'hello'在磁盘空间上开销是一样的。但是在选择上我们应该选择更短的列,因为更长的列会消耗更多的内存,因为MySQL通常会分配固定大小的内存块来保存内部值。尤其是使用内存临时表进行排序或操作时会特别糟糕。在利用磁盘临时表进行排序时也同样糟糕。所以最好的策略是只分配真正需要的空间。
BLOB和TEXT类型
- BLOB和TEXT都是为存储很大数据而设计的字符串数据类型,分别采用二进制和字符方式存储。
与其他类型不同,MySQL把每个BLOB和TEXT值当作一个独立的对象处理。存储引擎在存储时通常会做特殊处理。当BLOB和TEXT值太大时,InnoDB会使用专门的“外部”存储区域来进行存储,此时每个值在行内需要1~4个字节存储一个指针,然后在外部存储区域存储实际的值。
- BLOB和TEXT区别:BLOB类型存储的是二进制数据,没有排序规则或字符集,而TEXT类型有字符集和排序规则。
- 使用使用BLOB和TEXT注意:
- 尽量避免使用BLOB和TEXT类型, BLOB和TEXT值会引起一些性能问题
- 如果非用不可,建议把BLOB或TEXT 列分离到单独的表中
- 尽量避免检索大型的 BLOB或TEXT值。如:SELECT *查询,除非能够确定作为约束条件的WHERE子句只会找到所需要的数据行。否则,很可能毫无目的地在网络上传输大量的值。建议可以搜索索引列,决定需要的哪些数据行,然后从符合条件的数据行中检索BLOB或 TEXT值
- 可以使用合成的(Synthetic)索引来提高大文本字段(BLOB或TEXT)的查询性能。简单来说,合成索引就是根据大文本字段的内容建立一个散列值,并把这个值存储在单独的数据列中,接下来就可以通过检索散列值找到数据行了。但是,要注意这种技术只能用于精确匹配的查询(散列值对于类似“<”或“>=”等范围搜索操作符是没有用处的)。可以使用MD5函数生成散列值,也可以使用SHA-1(安全散列算法1)或CRC32(CRC32: CRC本身是“冗余校验码”的意思,CRC32则表示会产生一个32bit(8位十六进制数)的校验值),或者使用自己的应用程序逻辑来计算散列值。
枚举类型
- 如果表中字段的取值是固定几个字符串,可以使用枚举列代替常用的字符串类型。
- 枚举列可以把一些不重复的字符串存储成一个预定义的集合。MySQL在存储枚举时非常紧凑,会根据列表值的数量压缩到一个或者两个字节中,MySQL在内部会将每个值在列表中的位置保存为整数,这样就可以让表的大小大大缩小。
- 如下为枚举类型的创建:
CREATE TABLE shirts (
name VARCHAR(40),
size ENUM('x-small', 'small', 'medium', 'large', 'x-large')
);
INSERT INTO shirts (name, size) VALUES ('dress shirt','large'), ('t-shirt','medium'),
('polo shirt','small');
SELECT name, size FROM shirts WHERE size = 'medium';
+---------+--------+
| name | size |
+---------+--------+
| t-shirt | medium |
+---------+--------+
UPDATE shirts SET size = 'small' WHERE size = 'large';
COMMIT;
- 注意:
- 因为枚举列实际存储为整数,而不是字符串,所以不要使用数字作为ENUM枚举常量,这种双重性很容易导致混乱,例如ENUM( '1', '2', '3')。
- 枚举字段是按照内部存储的整数而不是定义的字符串进行排序的,所以尽量按照需要的顺序来定义枚举列。
日期和时间类型
- MySQL可以使用许多类型来保存日期和时间值,例如YEAR和 DATE以及DATETIME和TIMESTAMP。MySQL能存储的最小时间粒度为秒。
- DATETIME和TIMESTAMP区别:
- DATETIME存储日期范围:1001年~9999年
- TIMESTAMP存储日期范围:1970年~2038年,并且跟时区有关系
- 如何存储比秒更小粒度的日期和时间值?MySQL目前没有提供合适的数据类型,但是可以使用自己的存储格式:可以使用BIGINT类型存储微秒级别的时间截,或者使用DOUBLE存储秒之后的小数部分