选择优化的数据类型
- 更小的通常更好
- 简单为好:整型数据比字符型数据的比较操作代价更低。这里有两个例子:一个是应该将日期和时间存储为MySQL的内置类型而不是字符串类型,另外一个是应该用整型数据存储IP地址。
- 尽量避免存储NULL:通常情况下最好指定列为NOT NULL,除非明确需要存储NULL值。
整数类型
TINYINT、SMALLINT、MEDIUMINT、INT或BIGINT。它们分别使用8、16、24、32和64位存储空间。可以存储的值的范围从-2^(N-1)到2^(N-1)-1,其中N是存储空间的位数。
整数类型有可选的UNSIGNED属性,表示不允许负值,这大致可以使正数的上限提高一倍。
有符号和无符号类型使用相同的存储空间,并具有相同的性能,因此可以根据数据实际范围选择合适的类型。
实数类型
实数是带有小数部分的数字,也可以使用DECIMAL存储比BIGINT还大的整数。
有两种方式可以指定浮点列所需的精度,这可能会导致MySQL以静默方式选择不同的数据类型,或者在存储值时对其进行近似处理。这些精度说明符是非标准的,因此建议只指定数据类型,不指定精度。
浮点类型通常比DECIMAL使用更少的空间来存储相同范围的值。FLOAT列使用4字节的存储空间。DOUBLE占用8字节,比FLOAT具有更高的精度和更大的值范围。MySQL会使用DOUBLE进行浮点类型的内部计算。
由于额外的空间需求和计算成本,应该尽量只在对小数进行精确计算时才使用DECIMAL
字符串类型
VARCHAR
VARCHAR用于存储可变长度的字符串,是最常见的字符串数据类型。它比固定长度的类型更节省空间,因为它仅使用必要的空间(即,更少的空间用于存储更短的值)。
VARCHAR(5)和VARCHAR(200)存储’hello’的空间开销是一样的。但使用更短的列有很大的优势。较大的列会使用更多的内存,因为MySQL通常会在内部分配固定大小的内存块来保存值。这对于使用内存临时表的排序或操作来说尤其糟糕。在利用磁盘临时表进行文件排序时也同样糟糕。最好的策略是只分配真正需要的空间。
CHAR
CHAR是固定长度的:MySQL总是为定义的字符串长度分配足够的空间。
CHAR适合存储非常短的字符串,或者适用于所有值的长度都几乎相同的情况。CHAR也比VARCHAR更好,因为固定长度的行不容易出现碎片。对于非常短的列,CHAR也比VARCHAR更高效
BINARY和VARBINARY
存储的是二进制字符串。与常规字符串非常相似,但它们存储的是字节而不是字符。填充也不同:MySQL填充BINANRY用的是\0(零字节)而不是空格,并且在检索时不会去除填充值(如果需要在检索后保持值不变,请小心使用BINARY类型,MySQL会使用\0将其填充到需要的长度。)。
当需要存储二进制数据,并且希望MySQL将值作为字节而不是字符进行比较时,这些类型非常有用。字节比较的优势不仅仅是大小写不敏感。MySQL比较BINARY字符串时,每次按一个字节,并且根据该字节的数值进行比较。因此,二进制比较比字符比较简单得多,因此速度更快。
BLOB和TEXT类型
BLOB和TEXT都是为存储很大的数据而设计的字符串数据类型,分别采用二进制和字符方式存储。
如果可以避免的话,不要存储像图像数据作为BLOB数据。
使用枚举代替字符串类型
在存储枚举时非常紧凑,会根据列表值的数量压缩到1或者2字节中。
如果使用数字作为ENUM常量,这种双重属性很容易导致混乱,例如,ENUM(’1’,’2’ ,’3’)。尽量避免这么做。
日期和时间类型
DATETIME
从1000年到9999年,精度为1微秒。它以YYYYMMDDHHMMSS格式存储压缩成整数的日期和时间,且与时区无关。需要8字节的存储空间。
TIMESTAMP
存储自1970年1月1日格林尼治标准时间(GMT)午夜以来经过的秒数——与UNIX时间戳相同。TIMESTAMP只使用4字节的存储空间:只能表示从1970年到2038年1月19日。
默认情况下,当插入一行记录时没有指定第一个TIMESTAMP列的值,MySQL会将该列的值设置为当前时间。当更新一行记录时没有指定第一个TIMESTAMP列的值,MySQL默认也会将该列的值更新为当前时间。可以为任何TIMESTAMP列配置插入和更新行为。在默认情况下为NOT NULL
位压缩数据类型
BIT
存储一个或多个true/false值。BIT(1)定义一个包含1位的字段,BIT(2)存储2位的字段,依此类推;BIT列的最大长度为64位。
建议谨慎使用BIT类型。对于大多数应用来说,最好避免使用这种类型。
如果想在1位的存储空间中存储true/false值,另一个方法是创建一个可为空的CHAR(0)列。该列可以存储空值(NULL)或长度为零的值(空字符串)。这在实践中是可行的,但可能对使用数据库中该数据的其他人来说是难以理解的,并且使编写查询变得困难。除非非常注重节省空间,否则仍然建议使用TINYINT。
SET
如果需要存储多个true/false值,可以考虑使用MySQL原生的SET数据类型,可以将多列组合成一列,这在MySQL内部是以一组打包的位的集合来表示的。可以更有效地利用存储空间
JSON数据类型
新手在查看JSON数据类型时,可能会发现这是避免创建和管理独立字段的捷径。哪种方法更好在很大程度上是主观的。
JSON数据类型将使用更多空间来存储用于定义JSON的额外字符(大括号、方括号、冒号等)以及空格。
使用的表空间总量似乎是使用SQL列而不是存储原始JSON文档的主要驱动因素。使用SQL列的速度仍然更好。总的来说,决定使用原生SQL还是JSON取决于在数据库中存储JSON的便捷性是否大于性能。
选择标识符
标识符是引用行及通常使其唯一的方式。
- 为标识符列选择合适的数据类型非常重要。与其他列相比,更有可能将标识符列与其他值(例如,在联接中)进行比较,并使用它们进行查找。标识符列也可能在其他表中作为外键,因此为标识符列选择数据类型时,应该与联接表中的对应列保持一致。
- 在为标识符列选择类型时,不仅需要考虑存储类型,还需要考虑MySQL如何对该类型执行计算和比较。
- 确保在所有相关表中使用相同的类型。类型应该完全匹配,包括UNSIGNED等属性。混合不同的数据类型可能导致性能问题。在进行比较操作时,隐式类型转换也可能会产生难以发现的错误。
- 在可以满足值的范围的需求,并且预留未来增长空间的前提下,应该选择最小的数据类型。
整数类型
整数通常是标识符的最佳选择,因为它们速度快,并且可以自动递增。
AUTO INCREMENT是一个列属性,可以为新的行自动生成一个整数类型的值。
ENUM和SET类型
通常是糟糕的选择,尽管对某些只包含固定状态或者类型的静态“定义表”来说可能是没有问题的。
字符串类型
如果可能,应避免使用字符串类型作为标识符的数据类型,因为它们很消耗空间,而且通常比整数类型慢。
对于完全“随机”的字符串要非常小心,如MD5()、SHA1()或UUID()生成的字符串。这些函数生成的新值会任意分布在很大的空间内,这会减慢INSERT和某些类型的SELECT查询的速度
如果存储通用唯一标识符(UUID)值,则应该删除破折号,或者更好的做法是,使用UNHEX()函数将UUID值转换为16字节的数字,并将其存储在一个BINARY(16)列中。可以使用HEX()函数以十六进制格式检索值。
特殊数据类型
某些类型的数据并不直接对应于可用的内置类型。通常使用VARCHAR(15)列来存储IP地址。然而,它们实际上是32位无符号整数,而不是字符串。用小数点将地址分成四段的表示方法只是为了让人们阅读容易,所以应该将IP地址存储为无符号整数。MySQL提供了INET_ATON()和INET_NTOA()函数来在这两种表示形式之间进行转换。使用的空间从VARCHAR(15)的约16字节缩减到无符号32位整数的4字节。如果你担心数据库的可读性,不想继续使用函数查看行数据,请记住MySQL有视图,可以使用视图来简化数据查看的复杂性。
MySQL schema设计中的陷阱
- 太多的列
- 太多的联接
- 要小心过度使用ENUM
- 变相的枚举:ENUM列允许在列中保存一组已定义值中的单个值。SET列则允许在列中保存一组已定义值中的一个或多个值。有时很容易混淆。如果这里真和假两种情况不会同时出现,那么毫无疑问应该使用ENUM列而不是SET列。
- NULL不是虚拟值:即使需要在表中存储事实上的“空值”,也可能不需要使用NULL。也许可以使用0、特殊值或空字符串作为代替。但是遵循这个原则也不要走极端。
小结
- 尽量避免在设计中出现极端情况,例如,强制执行非常复杂的查询或者包含很多列的表设计(很多的意思是介于有点多和非常多之间)。
- 使用小的、简单的、适当的数据类型,并避免使用NULL,除非确实是对真实数据进行建模的正确方法。
- 尝试使用相同的数据类型来存储相似或相关的值,尤其是在联接条件中使用这些值时。
- 注意可变长度字符串,它可能会导致临时表和排序的全长内存分配不乐观。
- 如果可能的话,尝试使用整数作为标识符。
- 避免使用一些传统的MySQL技巧,例如,指定浮点数的精度或整数的显示宽度。
- 小心使用ENUM和SET类型。它们很方便,但也可能被滥用,有时还很棘手。另外最好避免使用BIT类型。