Fork me on GitHub

《高性能Mysql》第四章、Schema与数据类型优化

良好的逻辑设计和物理设计是高性能的基础,应该根据系统将要执行的查询语句来设计schema,这往往需要权衡各种因素。

选择优化的数据类型

Mysql支持的数据类型非常多,选择正确的数据类型对于获得高性能至关重要。不管存储哪种类型的数据,下面几个简单的原则都有助于做出更好的选择。

  • 更小的通常更好

一般情况下,应该尽量使用可以正确存储数据的最小数据类型。

  • 简单就好

简单数据类型的操作通常需要更少的CPU周期。

  • 尽量避免NULL

很多表都可包含为NULL(空值)的列,即使应用程序并不需要保存NULL也是如此,这是因为可为NULL是列的默认属性。通常情况下最好指定列为NOT NULL,除非真的需要存储NULL值。

在为列选择数据类型时,第一步需要确定合适的大类型:数字、字符串、时间等。

下一步是选择具体类型。很多Mysql的数据类型可以存储相同类型的数据,只是存储的长度和范围不一样、允许的精度不同,或者需要的物理空间(磁盘和内存空间)不同。相同大类型的不同子类型数据有时也有一些特殊的行为和属性。

整数类型

有两种类型的数字:整数和实数。如果存储整数,可以使用这几种整数类型:TINYINT,SMALLINT,MEDIUMINT,INT,BIGINT。分别使用8,16,24,32,64位存储空间。它们可以存储的值得范围从2(n1)-2^{\left( n-1\right) }2(n1)12^{\left( n-1\right) }-1,其中N是存储空间的位数。

整数类型有可选的UNSIGNED属性,表示不允许空值,这大致可以使正数的上限提高一倍。

实数类型

实数是带有小数部分的数字。然而,它们不只是为了存储小数部分;也可以使用DECIMAL存储比BIGINT还大的整数。Mysql既支持精确类型,也支持不精确类型。

FLOATDOUBLE类型支持使用标准的浮点运算进行近似计算。DECIMAL类型用于存储精确地小数。浮点和DECIMAL类型都可以指定精度。

浮点类型在存储同样范围的值时,通常比DECIMAL使用更少的空间。FLOAT使用4个字节存储。DOUBLE占用8个字节。

字符串类型

VARCHAR和CHAR类型

VARCHARCHAR是两种最主要的字符串类型。

关于两种类型的一些比较。

  • VARCHAR

VARCHAR类型用于存储可变长字符串,是最常见的字符串数据类型。它比定长类型更节省空间,因为它仅使用必要的空间。

**VARCHAR需要使用1或2个额外字节记录字符串的长度:如果列的最大长度小于或等于255字节,则只使用1个字节表示,否则使用2个字节。**假设使用latin1字符集,一个VARCHAR(10)的列需要11个字节的存储空间。VARCHAR(1000)的列则需要1002个字节,因为需要2个字节存储长度信息。

下面这些情况下使用VARCHAR是合适的:字符串列的最大长度比平均长度大很多;列的更新更少,所以碎片不是问题;使用了像UTF-8这样复杂的字符集,每个字符都使用不同的字节数进行存储。

  • CHAR

CHAR类型是定长的:Mysql总是根据定义的字符串长度分配足够的空间。当存储CHAR值时,Mysql会删除所有的末尾空格。CHAR值会根据需要采用空格进行填充以方便比较。

**CHAR适合存储很短的字符串,或者所有值都接近同一个长度。对于经常变更的数据,CHAR也比VARCHAR更好,因为定长的CHAR类型不容易产生碎片。对于非常短的列,CHARVARCHAR在存储空间上也更有效率。**例如用CHAR(1)来存储只有Y和N的值,如果采用单字节字符集只需要一个字节,但是VARCHAR(1)却需要两个字节,因为还有一个记录长度的额外字节。

CHARVARCHAR类似的类型还有BINARYVARBINARY,它们存储的是二进制字符串。二进制字符串跟常规字符串非常相似,但是二进制字符串存储的是字节码而不是字符。填充也不一样:Mysql填充BINARY采用的是\0(零字节)而不是空格,在检索时也不会去掉填充值。

使用VARCHAR(5)VARCHAR(200)存储‘hello’的空间开销是一样的。那么使用更短的列有什么优势吗?

事实证明有很大优势。更长的列会消耗更多的内存,因为Mysql通常会分配固定大小的内存块来保存内存值。尤其是使用内存临时表进行排序或操作时会特别糟糕。在利用磁盘临时表进行排序时也同样糟糕。所以最好的策略是只分配真正需要的空间。

BLOB和TEXT类型

BLOBTEXT都是为存储很大的数据而设计的字符串数据类型,分别采用二进制和字符方式存储。

字符类型是TINYTEXT,SMALLTEXT,TEXT,MEDIUMTEXT,LONGTEXT;对应的二进制类型是TINYBLOB,SMALLBLOB,BLOB,MEDIUMBLOB,LONGBLOBBLOBSMALLBLOB的同义词,TEXTSMALLTEXT的同义词。

与其他类型不同,Mysql把每个BLOBTEXT值当作一个独立的对象处理。存储引擎在存储时通常会做特殊处理。当BLOBTEXT值太大时,InnoDB会使用专门的外部存储区域来进行存储,此时每个值在行内需要1~4个字节存储指针,然后在外部存储区域存储实际值。

BLOBTEXT家族之间仅有的不同是BLOB类型存储的是二进制数据,没有排序规则或字符集,而TEXT字符有字符集和排序规则。

Mysql不能将BLOBTEXT列全部长度的字符串进行索引,也不能使用这些索引消除排序。

使用枚举(ENUM)代替字符串类型

有时候可以使用枚举列代替常用的字符串类型。枚举列可以把一些不重复的字符串存储成一个预定义的集合。

日期和时间类型

Mysql可以使用许多类型来保存日期和时间值,例如YEAR和DATE。Mysql能存储的最小时间粒度为秒。但是Mysql也可以使用微秒级的粒度进行临时运算。

DATETIME

这个类型能保存大范围的值,从1001年到9999年,精度为秒。它把日期和时间封装到格式为YYYYMMDDHHMMSS的整数中,与时区无关。使用8个字节的存储空间。

默认情况下,Mysql以一种可排序的、无歧义的格式显示DATETIME值,例如“2008-01-16 22:37:08”。这是ANSI标准定义的日期和时间表示方法。

TIMESTAMP

TIMESTAMP类型保存了从1970年1月1日午夜(格林尼治标准时间)以来的秒数,它和UNIX时间戳相同。TIMESTAMP只使用4个字节的存储空间,因此它的范围比DATETIME小得多:只能表示从1970年到2038年。Mysql提供了FROM_UNIXTIME()函数把Unix时间戳转换为日期,并提供了UNIX_TIMESTAMP()函数把日期转换为Unix时间戳。

TIMESTAMP显示的值依赖于时区。Mysql服务器、操作系统,以及客户端连接都有时区设置。

TIMESTAMPDATETIME没有的特殊属性。默认情况下,如果插入时没有指定第一个TIMESTAMP列的值,Mysql则设置这个列的值为当前时间。再更新一条记录时,Mysql默认也会更新第一个TIMESTAMP列的值(除非在UPDATE语句中明确指定了值)。TIMESTAMP列默认为NOT NULL,这也和其他的数据类型不一样。

除了特殊行为外,通常也应该尽量使用TIMESTAMP,因为它比DATETIME空间效率更高。

位数据类型

Mysql有少数几种存储类型使用紧凑的位存储数据。所有这些位类型,从技术上来说都是字符串类型。

BIT

可以使用BIT列在一列中存储一个或多个``true/false值。BIT(1)定义一个包含单个位的字段,BIT(2)存储两个位,依此类推。BIT`列的最大长度是64个位。

应该谨慎使用BIT类型。对于大部分应用,最好避免使用这种类型。

如果想在一个bit的存储空间中存储一个true/false值,另一个方法是创建一个可以为空的CHAR(0)列。该列可以保存空值(NULL)或者长度为零的字符串(空字符串)。

SET

如果需要保存很多true/false值,可以考虑合并这些列到一个SET数据类型,它在Mysql内部是以一系列打包的位的集合来表示的。

选择标识符

  • 整数类型

整数通常是标识列最好的选择,因为它们很快并且可以使用AUTO_INCREMENT

  • ENUM和SET类型

对于标识列来说,ENUMSET类型通常是一个糟糕的选择。

  • 字符串类型

如果可能,应该避免使用字符串类型作为标识列,因为它们很消耗空间,并且通常比数字类型慢。

如果存储UUID值,则应该移除“-”符号,或者更好的做法是,用UNHEX()函数转换UUID值为16字节的数字,并且存储在一个BINARY(16)列中。检索时可以通过HEX()函数来格式化为十六进制格式。

Mysql schema设计中的陷阱

  • 太多的列
  • 太多的关联(一个粗略的经验法则,如果希望执行得快速其并发性好,单个查询最好在12个表以内做关联)
  • 全能的枚举(注意防止过度使用枚举)
  • 变相的枚举
  • 非此发明的NULl(当确实需要表示未知值时也不要害怕使用NULL

总结

  • 尽量避免过度设计
  • 使用小而简单的合适数据类型
  • 尽量使用相同的数据类型存储相似或相关的值
  • 注意可变长字符串,其在临时表和排序时可能导致悲观的按最大长度分配内存
  • 尽量使用整型定义标识列
  • 避免使用Mysql已经遗弃的特性
  • 小心使用ENUMSET
  • 范式是好的,但是反范式有时也是必需的,并且能带来好处
  • ALTER TABLE是让人痛苦的操作,因为在大部分情况下,它都会锁表并且重建整张表
求鼓励,求支持!