共计 3583 个字符,预计需要花费 9 分钟才能阅读完成。
良好的逻辑设计和物理设计是高性能的基石,应该根据系统将要执行的查询语句来设计 schema, 这往往需要权衡各种因素。
MySQL 支持的数据类型非常多,选择正确的数据类型对于获得高性能至关重要。
更小的通常更好
更小的数据类型通常更快,因为它们占用更少的磁盘、内存和 CPU 缓存,并且处理时需要的 CPU 周期也更少。
简单就好
简单数据类型的操作通常需要更少的 CPU 周期。例如,整型比字符操作代价更低,因为字符集和校对规则(排序规则)使字符比较比整型比较更复杂。
尽量避免 NULL
如果查询中包含可为 NULL 的列,对 MySQL 来说更难优化,因为可为 NULL 的列使得索引、索引统计和值比较都更复杂。可为 NULL 的列会使用更多的存储空间,在 MySQL 里也需要特殊处理。当可为 NULL 的列被索引时,每个索引记录需要一个额外的字节,在 MyISAM 里甚至还可能导致固定大小的索引(例如只有一个整数列的索引)变成可变大小的索引。
当然也有例外,例如 InnoDB 使用单独的位 (bit) 存储 NULL 值,所以对于稀疏数据有很好的空间效率。
1. 整数类型
有两种类型的数字:整数 (whole number) 和实数 (real number)。如果存储整数,可以使用这几种整数类型:TINYINT, SMALLINT, MEDIUMINT, INT, BIGINT。分别使用 8,16, 24, 32, 64 位存储空间。
整数类型有可选的 **UNSIGNED ** 属性,表示不允许负值,这大致可以使正数的上限提高一倍。例如 TINYINT. UNSIGNED 可以存储的范围是 0 – 255, 而 TINYINT 的存储范围是 -128 -127。
有符号和无符号类型使用相同的存储空间,并具有相同的性能,因此可以根据实际情况选择合适的类型。
你的选择决定 MySQL 是怎么在内存和磁盘中保存数据的。然而,整数计算一般使用 64 位的 BIGINT 整数,即使在 32 位环境也是如此。(一些聚合函数是例外,它们使用 DECIMAL 或 DOUBLE 进行计算)。
MySQL 可以为整数类型指定宽度,例如 INT(11), 对大多数应用这是没有意义的:它不会限制值的合法范围,只是规定了 MySQL 的一些交互工具(例如 MySQL 命令行客户端)用来显示字符的个数。对于存储和计算来说,INT(1) 和 INT(20) 是相同的。
2. 实数类型
实数是带有小数部分的数字。然而,它们不只是为了存储小数部分,也可以使用 DECIMAL 存储比 BIGINT 还大的整数。
FLOAT 和 DOUBLE 类型支持使用标准的浮点运算进行近似计算。
DECIMAL 类型用于存储精确的小数。
浮点和 DECIMAL 类型都可以指定精度。对于 DECIMAL 列,可以指定小数点前后所允许的最大位数。这会影响列的空间消耗。
有多种方法可以指定浮点列所需要的精度,这会使得 MySQL 选择不同的数据类型,或者在存储时对值进行取舍。这些精度定义是非标准的,所以我们建议只指定数据类型,不指定精度。
浮点类型在存储同样范围的值时,通常比 DECIMAL 使用更少的空间。FLOAT 使用 4 个字节存储。DOUBLE 占用 8 个字节,相比 FLOAT 有更高的精度和更大的范围。和整数类型一样,能选择的只是存储类型;MySQL 使用 DOUBLE 作为内部浮点计算的类型。
因为需要额外的空间和计算开销,所以应该尽量只在对小数进行精确计算时才使用 DECIMAL。但在数据最比较大的时候,可以考虑使用 BIGINT 代替 DECIMAL, 将需要存储的货币单位根据小数的位数乘以相应的倍数即可。
3. 字符串类型
VARCHAR
- 用于存储可变⻓字符串,长度支持到 65535
- 需要使用 1 或 2 个额外字节记录字符串的长度
- 适合:字符串的最大⻓度比平均⻓度⼤很多;更新很少
CHAR
- 定⻓,⻓度范围是 1~255
- 适合:存储很短的字符串,或者所有值接近同一个长度;经常变更
慷慨是不明智的
使用 VARCHAR(5) 和 VARCHAR(200) 存储 ’hello’ 的空间开销是一样的。那么使用更短的列有什么优势吗?
事实证明有很大的优势。更长的列会消耗更多的内存,因为 MySQL 通常会分配固定大小的内存块来保存内部值。尤其是使用内存临时表进行排序或操作时会特别糟糕。在利用磁盘临时表进行排序时也同样糟糕。
所以最好的策略是只分配真正需要的空间。
4.BLOB 和 TEXT 类型
BLOB 和 TEXT 都是为存储很大的数据而设计的字符串数据类型,分别采用 二进制和字符方式存储。
与其他类型不同,MySQL 把每个 BLOB 和 TEXT 值当作一个独立的对象处理。存储引擎在存储时通常会做特殊处理。当 BLOB 和 TEXT 值太大时,InnoDB 会使用专门的“外部“存储区域来进行存储,此时每个值在行内需要 1 – 4 个字节存储 存储区域存储实际的值。
BLOB 和 TEXT 之间仅有的不同是 BLOB 类型存储的是二进制数据,没有排序规则或字符集,而 TEXT 类型有字符集和排序规则
5. 日期和时间类型
大部分时间类型都没有替代品,因此没有什么是最佳选择的问题。唯一的问题是保存日期和时间的时候需要做什么。MySQL 提供两种相似的日期类型:DATE TIME 和 TIMESTAMP。
但是目前我们更建议存储时间戳的方式,因此该处不再对 DATE TIME 和 TIMESTAMP 做过多说明。
6. 其他类型
6.1 选择标识符
在可以满足值的范围的需求,井且预留未来增长空间的前提下,应该选择最小的数据类型。
- 整数类型
整数通常是标识列最好的选择,因为它们很快并且可以使用 AUTO_INCREMENT。
- ENUM 和 SET 类型
对于标识列来说,EMUM 和 SET 类型通常是一个糟糕的选择,尽管对某些只包含固定状态或者类型的静态”定义表”来说可能是没有问题的。ENUM 和 SET 列适合存储固定信息,例如有序的状态、产品类型、人的性别。
- 字符串类型
如果可能,应该避免使用字符串类型作为标识列,因为它们很消耗空间,并且通常比数字类型慢。
对于完全“随机”的字符串也需要多加注意,例如 MDS()、SHAl() 或者 UUID() 产生的字符串。这些函数生成的新值会任意分布在很大的空间内,这会导致 INSERT 以及一些 SELECT 语句变得很慢。如果存储 UUID 值,则应该移除 “-“ 符号。
6.2 特殊类型数据
某些类型的数据井不直接与内置类型一致。低千秒级精度的时间戳就是一个例子,另一个例子是以个 1Pv4 地址,人们经常使用 VARCHAR(15) 列来存储 IP 地址,然而,它们实际上是 32 位无符号整数,不是字符串。用小数点将地址分成四段的表示方法只是为了让人们阅读容易。所以应该用无符号整数存储 IP 地址。MySQL 提供 INET_ATON() 和 INET_NTOA() 函数在这两种表示方法之间转换。
1. 范式和反范式
对于任何给定的数据通常都有很多种表示方法,从完全的范式化到完全的反范式化,以及两者的折中。在范式化的数据库中,每个事实数据会出现并且只出现一次。相反,在反范式化的数据库中,信息是冗余的,可能会存储在多个地方。
范式的优点和缺点
为性能提升考虑时,经常会被建议对 schema 进行范式化设计,尤其是写密集的场景。
- 范式化的更新操作通常比反范式化要快。
- 当数据较好地范式化时,就只有很少或者没有重复数据,所以只需要修改更少的数据。
- 范式化的表通常更小,可以更好地放在内存里,所以执行操作会更快。
- 很少有多余的数据意味着检索列表数据时更少需要 DISTINCT 或者 GROUP BY 语句。
反范式的优点和缺点
不需要关联表,则对大部分查询最差的情况——即使表没有使用索引——是全表扫描。当数据比内存大时这可能比关联要快得多,因为这样避免了随机 I /0。
单独的表也能使用更有效的索引策略。
混用范式化和反范式化
在实际应用中经常需要混用,可能使用部分范式化的 schema、缓存表,以及其他技巧。
表适当增加冗余字段,如性能优先,但会增加复杂度。可避免表关联查询。
简单熟悉数据库范式
第一范式 (1NF):字段值具有原子性, 不能再分 (所有关系型数据库系统都满足第一范式);
例如:姓名字段, 其中姓和名是一个整体, 如果区分姓和名那么必须设立两个独立字段;
第二范式 (2NF):一个表必须有主键, 即每行数据都能被唯一的区分;
备注:必须先满足第一范式;
第三范式 (3NF):一个表中不能包涵其他相关表中非关键字段的信息, 即数据表不能有沉余字段;
备注:必须先满足第二范式;
2. 表字段少精
- I/ O 高效
- 字段分开维护简单
- 单表 1G 体积 500W ⾏行评估
- 单⾏行不超过 200Byte
- 单表不超过 50 个 INT 字段
- 单表不超过 20 个 CHAR(10) 字段
- 建议单表字段数控制在 20 个以内
- 拆分 TEXT/BLOB,TEXT 类型处理性能远低于 VARCHAR,强制生成硬盘临时表浪费更多空间。