MySql Schema与数据类型优化

一、数据类型的选择原则

  • 更小的通常更好

1.更小的数据类型通常更快,因为它们占用更少的磁盘、内存和CPU缓存,并且处理时需要的CPU周期也更少。

2.要确保没有低估需要存储值得范围,因为在schema中的多个地方增加数据类型的范围是一个非常耗时和痛苦的操作。如果无法确定哪个类型数据类型是最好的,就选择你认为不会超过范围的最小类型

  • 简单就好

简单数据类型的操作通常需要更少的CPU周期。例如,整型比字符操作代价更低,因为字符集和校对规则(排序规则)使字符比较比整型比较更复杂应该用MySql内建的类型而不是字符串来存储日期和时间,另外一个是应该用整型存储IP地址

  • 尽量避免使用NULL

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

2.如果查询中包含可为NULL的列,对MySql来说优化更难,因为可为NULL的列使索引、索引统计和值比较都更复杂而且会使用更多的空间,MySql也需要特殊处理当可为NULL的列别索引时,每个索引记录需要一个额外的字节,在MyISAM里甚至还可能导致固定大小的索引(列出只有一个整数列的索引)变成可大可小的索引

3.通常把可为NULL的列改为NOT NULL带来的性能提升比较小,所以在(调优时)没有必要首先在现有的schema中查找并修改掉这种情况,除非确定会导致问题。但是,如果计划在列上创建索引,就应该尽量避免设计成可为NULL的列

4.在InnoDB使用单独的位(bit)存储NULL值,所以对于稀疏数据(很多值为NULL,只有少数行的列有非NULL值)有很好的空间效率。不适用MyISAM。

二、整数类型

有两种类型的数字:整数(whole number)和实数(real number)。如果存储整数,可以使用这几种整数类型:TINYINT,SMALLINT,MEDIUMINT,INT,BIGINT。分别使用8,16,24,32,64位存储空间。数值范围如下,-2的N-1次方到2的N-1次方。

MySql Schema与数据类型优化

选择不同的数据类型,决定MySql在内存中和磁盘中是怎么保存数据的。整数计算一般是使用64位的BIGINT计算,即使在32位环境也是如此。(一些聚合函数是例外,它们使用DECIMAL或DOUBLE进行计算)。

MySql可以为整数类型指定宽度,例如INT(11),对大多数应用这是没有意义的:它不会限制值得范围,只是规定了MySql的一些交互工具。对于存储和计算来说,INT(1)和INT(20)是相同的。

INT类型的属性:

  UNSIGNED/SIGNED: 是否有符号

  ZEROFILL:  显示属性,值不做任何修改

  Auto_INCREMENT:  自增,每张表一个自增字段,该自增字段,必须是索引的一部分

三、实数类型

实数是带有小数部分的数字。它们不止是为了存储小数部分;也可以使用DECIMAL存储比BIGINT还大的整数。MySQL既支持精确类型,也支持不精确类型。(都使用DOUBLE作为内部浮点数计算的类型)

单精度类型: FLOAT

双精度类型: DOUBLE

高精度类型:DECIMAL

MySql Schema与数据类型优化

因为CPU不支持DECIMAL的直接计算,所以在MySQL 5.0和更高版本将数字打包保存到一个二进制字符串中(每四个字节存9个数字)。例如,DECIMAL(18,9)小数点两边将各存储9个数字,一共使用9个字节:小数点前4个字节,小数点后4个字节,小数点本身占1个字节。

因为需要额外的空间和计算开销,所以应该尽量只在对小数进行精确计算时才使用DECIMAL---例如存财务数据。但是在数据量比较大的时候,可以考虑使用BIGINT代替DECIMAL,需要存储的货币单位根据小数的位数乘以相应的倍数即可。假设要存财务数据精确到万分之一分,则可以把所有金额乘以一百万,让后将结果存储在BIGINT里,这样可以同时避免浮点计算存储计算不精确和DECIMAL精确计算代价高的问题。

四、字符串类型

MySQL支持多种字符串类型,每种类型还有很多种变种。从MySQL4.1开始,每个字符串类型还可以定义自己的字符集和排序规则,或者说校对规则(collation)。

MySql Schema与数据类型优化

VARCHAR和CHAR类型

两种最主要有的字符串类型,存储到磁盘和内存中的方式和具体的存储引擎有关,下面都是基于MyISAM/InnoDB。

  • VARCHAR

VARCHAR类型用于存储可变长字符串,是最常见的字符串数据类型。它比定长类型更节省空间,因为它仅使用必要的空间(例如,越短的字符串使用越少的空间)。有一种情况例外,如果MySQL表使用ROW_FORMAT=FIXED创建的话,每一行都会使用定长存储,这会浪费空间。

VARCHAR需要使用1或2个额外字节记录字符串的长度:如果列的最大长度小于或等于255字节,则只使用1个字节表示,否则使用2个字节表示

VARCHAR节省了存储空间,所以对性能也有帮助。但是,由于行是可变的,再UPDATE时可能使行变得比原来更长,这就导致额外的工作。如果一个行占用的空间增长,并且在业内,没有更多的空间可以存储,在这种情况下,不同的存储引擎的处理方式不一样。MyISAM会将行拆成不同的片段存储,InnoDB则需要分裂页来使行可以放进业内。

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

  • CHAR

CHAR类型是定长的:MySQL总是根据定义的字符串长度分配足够的空间。当存储CHAR值时,MySQL会删除所有的末尾空格(version < 4.1,VARCHAR也是如此)。CHAR会根据需要填充空格以方便比较。

CHAR非常适合存储很短的字符串,或者所有值都接近一个长度。例如,MD5的值,用CHAR(1)来存储只有Y和N的值。

  • BINARY

与CHAR和VARCHAR类似的类型还有BINARY和VARBINARY,它们存储的是二进制字符串。二进制字符串跟常规字符串非常相似,存储的是字节码而不是字符。填充也不一样:BINARY用\0(零字节)而不是空格。

当需要存储二进制数据,并且希望MySQL使用字节码而不是字符比较时,这些类型是非常有用的。二进制比较的优势不仅仅体现在大小写敏感上。BINARY比较字符串时,每次按一个字节,并且根据该字节数进行比较,相比较字符串简单的多,也更快。

  • 备注

数据如何存储取决于存储引擎,并非所有的存储引擎都会按照相同的方式处理定长和变长的字符串。Memory引擎只支持定长的行,即使有变长字段也会根据最大长度分配最大空间。填充和截取空格的行为在不同存储引擎都是一样的,因为这是在MySQL服务器层进行处理的。

使用VARCHAR(5)和VARCHAR(200)存储‘hello’的空间开销是一样的,但是,更长的列会消耗更多的内存,因为MySQL通常会分配固定大小的内存来保存内部值。尤其是使用内存临时表来进行排序或操作时特别糟糕。在利用磁盘临时表进行排序时也同样糟糕。所以最好的策略是只分配真正需要的空间。

  • BLOB和TEXT类型

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

实际上,它们分别属于两组不同的数据类型 家族:字符类型是TINYTEXT,SMALLTEXT,TEXT,MEDIUMTEXT,LONGTEXT;对应的二进制类型是TINYBLOB,SMALLBLOB,BLOB,MEDIUMBLOB,LONGBLOB。BLOB是SMALLBLOB的同义词,TEXT是SMALLTEXT的同义词。

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

注意:

1、在BLOB和TEXT列上创建索引时,必须指定索引前缀的长度

2、BLOB和TEXT列不能有默认值

3、BLOB和TEXT列排序只使用该列的前max_sort_length

五、日期和时间类型

MySql Schema与数据类型优化

大部分时间类型都没有替代品,因此没有什么事最佳选择的问题。唯一的问题是保存日期和时间的时候需要做什么。MySQL提供了两种相似的日期类型:DATETIME和TIMESTAMP。

  • DATETIME

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

  • TIMESTAMP

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

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

应该尽量使用TIMESTAMP,因为它比DATETIME空间效率更高。尽量不要使用整数存储时间戳,不方便处理