MySQL 数据库 Schema 设计的性能优化②:合适的数据类型
实际上在很多数据库的设计优化文档中都有关于通过优化数据类型的优化说明内容,在 MySQL 中,我们同样也可以通过数据类型的优化达到优化整个 Schema 设计的目的。
优化数据类型提高性能的主要原理在于以下几个方面:
- 通过选用更“小”的数据类型减少存储空间,使查询相同数据需要的 IO 资源降低;
- 通过合适的数据类型加速数据的比较;
下面我们还是通过分析一些常用数据类型的数据存储格式和长度来看看哪些数据类型可以在优化中利用上吧。
①数字日期类型
我们先来看看存放长度基本固定的一些数据类型的存储长度和取值范围。
对于数字类型,这里分别列出了整数类型和小数类型,也就是浮点数类型。实际上,还有一类通过二进制格式以字符串来存放的数字类型如 DECIMAL(DEC)[(M[,D])],NUMERIC[(M[,D])],由于其存放长度主要通过其定义时候的的 M 所决定,M 定义为多大,则实际存放就有多长。M 代表整个位数长度,而 D 则表示小数点后的位数,默认 M 为 10,D 为 0。一般来说,主要用在固定精度的场合,由于其存放长度较大,而且考虑到这种数据完全可以变化形式以整数存放,所以笔者个人并不是特别推荐。
对于数字的存储,一般使用到浮点型数据的场合也不应该太多。主要出于两个原因,一个是浮点型数据本身实际上是一个并不精确的数字,只是一个近似值,另一个原因就是完全可以通过乘以一个固定的系数转换为整型数据来存放。这样不仅可以解决数据不精确的问题,同时也让数据的处理更为高效。
时间存储格式总类并不是太多,我们常用的主要就是 DATETIME,DATE 和 TIMESTAMP 这三种了。从存储空间来看TIMESTAMP最少,四个字节,而其他两种数据类型都是八个字节,多了一倍。而 TIMESTAMP 的缺点在于它只能存储从1970 年之后的时间,而另外两种时间类型可以存放最早从1001 年开始的时间。如果有需要存放早于 1970 年之前的时间的需求,我们必须放弃 TIMESTAMP 类型,但是只要我们不需要使用 1970 年之前的时间,最好尽量使用 TIMESTAMP 来减少存储空间的占用。
上面所列出的主要是一些存放固定长度,且我们平时可能常用到的一些类型。通过这个对照表格,我们可以很直观地看出哪种类型占用的存储空间大,哪种占用的空间小。这样,在数据类型选择的时候,我们就可以结合各种类型的存储范围以及业务中可能存在的数据作出对应,然后选择存储空间最先的类型来使用。
②字符存储类型
我们再来看看存放字符的数据类型。
CHAR[(M)]类型属于静态长度类型,存放长度完全以字符数来计算,所以最终的存储长度是基于字符集的,如 latin1 则最大存储长度为 255 字节,但是如果使用 gbk 则最大存储长度为 510 字节。CHAR 类型的存储特点是不管我们实际存放多长数据,在数据库中都会存放 M 个字符,不够的通过空格补上,M 默认 为 1。虽然CHAR会通过空格补齐存放的空间,但是在访问数据的时候,MySQL 会忽略最后的所有空格,所以如果我们的实际数据中如果在最后确实需要空格,则不能使用CHAR 类型来存放。在MySQL5.0.3之前的版本中,如果我们定义 CHAR 的时候 M 值超过 255,MySQL 会自动将 CHAR 类型进行转换为可以存入对应数据量的 TEXT 类型,如 CHAR(1000)会自动转换为 TEXT,CHAR(10000)则会转为 MEDIUMTEXT。而从 MySQL5.0.3 开始,所有超过 255 的定义 MySQL 都会直接拒绝并给出错误信息,不再自动转换。
VARCHAR[(M)]属于动态存储长度类型,仅存占用实际存储数据的长度。其存放的最大长度与 MySQL 版本有关,在 5.0.3 之前的版本 VARCHAR 以字符数控制最存储的最大长度,最大只能存放 255 个字符,占用存储空间的实际大小与字符集有关。但是从 5.0.3 开始,VARCHAR 的最大存储限制已经更改为字节数限制了,扩展到可以存放 65535 bytes 的数据,不同的字符集可能存放的字符数并不一样。也就是说,在 MySQL5.0.3 之前的版本,M 所代表的是字符数,而从 5.0.3 版本开始,M 的代表意思已经是字节数了。VARCHAR 的存储特点是不管我们设定 M 为多大的值,真正占用的存储空间都只有我们所存入的实际数据的大小,和 CHAR 不同的是 VARCHAR会保留我们存入数据最后的空格,也就是说我们存入是什么样,MySQL 返回给我们的也会是什么样。在 VARCHAR 类型字段的数据中,MySQL 会在每个 VARCHAR 数据中使用 1 个或 者 2 个字节用来存放 VARCHAR 数据的实际长度,当我们的实际数据在 255 字节之内的时候,会使用 1 字节来存放实际长度,而大于 255 字节的时候,则需要使用 2 字节来存放。
TINYTEXT,TEXT,MEDIUMTEXT 和 LONGTEXT 这四种类型同属于一种存储方式,都是动态存储长度类型,不同的仅仅是最大长度的限制。四种类型的定义都是通过最大字符数来限制,但是他们的字符数限制实际上是可以理解为字节数限制的,因为当我们使用多字节字符集的时候,实际能存放的字符书并没最大字符数那么多,而是以单字节字符来计算的字符数。此外,由于是动态存储长度类型,所以和 VARCHAR 一样,每个字段数据之前都需要一个存放实际长度的空间。TINYTEXT需要1 个字节来存放,TEXT 需要 2 个字节,MEDIUMTEXT 和 LONGTEXT 则分别需要 3 个和 4 个字节来存放实际数据长度。实际上,除了 MySQL 内 嵌 的 最 大 长 度 限 制 之 外 , 他 们 还 爱 到 客 户 端 与 服 悟 器 端 的 网 络 通 信 缓 冲 区 最 大 值 (max_allowed_packet)的限制。
这四种 TEXT 类型和 CHAR 及 VARCHAR 在实际使用中存在几个不一样的地方:
- 不能设置默认值;
- 只有 TEXT 可以使用 TEXT[(M)]这样的方式通过 M 设置大小;
- 基于这四种类型的索引必须指定前缀长度;
③其他常用类型
除了上面这些字段类型之外会被我们经常使用到之外,我们还会使用到的数据类型主要有以下这些。
对于 BIT 类型,M 表示每个值得 bits 数目,默认为 1,最大为 64 bits。对于 MySQL 来说这是一个新的类型,因为从 MySQL5.0.3 才开始真正实现(在之前实际上是 TINYINT(1)),而且仅仅支持 MyISAM 存储引擎,但是从 MySQL5.0.5 开始 Memory,Innodb 和 NDB Cluster 存储引擎也开始“支持”了。在 MyISAM中,BIT的存储空间很小,是真正的实现了通过bit 来存储,但是在其他的一些存储引擎中就不一样了,因为他们是转换为最小的INT 类型存储的,所以占用的空间也没有节省,还不如直接使用 INT 类的数据类型存放来得直观。
对于 SET 和 ENUM 类型,主要内容基本处于较少变化状态且值比较少的字段。虽然这两个字段所占用的存储空间都较少,但是由于在使用方面较其他的数据类型要略为复杂一些,所以在实际环境中一般使用还是较少。
谁都知道,数据量(这里主要指数据记录条数)的增加肯定会让数据库的检索查询效率降低。所以很多时候人们大都希望通过减少数据库中关键表的记录条数来获得数据库性能的提升。实际上,除了这种通过控制数据记录条数来控制数据总量的办法之外,我们还可以通过选择更小的数据类型来让数据库通过更小的空间存放相同的数据量,这对于检索同样的数据所带来的 IO 消耗自然会降低,性能也就很自然得到了提升。
此外,由于 CPU 对不同数据的处理方式不一样,就会造成不同类型的数据在各种运算处理如比较,排序等方面的处理效率存在差异。所以,对于我们需要经常进行比较计算以及排序等消耗 CPU 资源的字段,应该尽量选择处理更为迅速的字段类型。如通过整数类型代替浮点数或者字符类型。