良好的逻辑设计和物理设计是高性能的基石,应该依据零碎将要执行的查问语句来设计schema,这往往须要衡量各种因素。例如,反范式的设计能够放慢某些类型的查问,但同时可能使另一些类型的查问变慢。比方增加计数表和汇总表是一种很好的优化查问的形式,但这些表的保护老本可能会很高。MySQL独有的个性和实现细节对性能的影响也很大。
抉择优化的数据类型
MySQL反对的数据类型十分多,抉择正确的数据类型对于取得高性能至关重要。不论存储哪种类型的数据,上面几个简略的准则都有助于做出更好的抉择。
更小的通常更好
个别状况下,应该尽量应用能够正确存储数据的最小数据类型。更小的数据类型通常更快,因为它们占用更少的磁盘、内存和CPU缓存,并且解决时须要的CPU周期也更少。
简略就好
简略数据类型的操作通常须要更少的CPU周期。例如,整型比字符操作代价更低,因为字符集和校对规定(排序规定)使字符比拟比整型比拟更简单。这里有两个例子:一个是应该应用MySQL内建的类型而不是字符串来存储日期和工夫,另外一个是应该用整型存储IP地址。
尽量避免NULL
如果查问中蕴含可为NULL的列,对MySQL来说更难优化,不应用NULL的理由有:
- 所有应用NULL值的状况,都能够通过一个有意义的值的示意,这样有利于代码的可读性和可维护性,并能从束缚上加强业务数据的规范性。
- NULL值到非NULL的更新无奈做到原地更新,更容易产生索引决裂,从而影响性能。(null -> not null性能晋升很小,除非确定它带来了问题,否则不要当成优先的优化措施)
- NULL值在timestamp类型下容易出问题,特地是没有启用参数explicit_defaults_for_timestamp。
- NOT IN、!= 等负向条件查问在有 NULL 值的状况下返回永远为空后果,查问容易出错。
- NULL会使索引、索引统计和值比拟都更加简单,并且在MyISIM中须要额定一个字节的存储空间。
在为列抉择数据类型时,第一步须要确定适合的大类型:数字、字符串、工夫等,下一步是抉择具体类型。很多MySQL的数据类型能够存储雷同类型的数据,只是存储的长度和范畴不一样、容许的精度不同,或者须要的物理空间(磁盘和内存空间)不同。雷同大类型的不同子类型数据有时也有一些非凡的行为和属性。
整数类型
如果存储整数,能够应用这几种整数类型:TINYINT,SMALLINT,MEDIUMINT,INT,BIGINT。别离应用8,16,24,32,64位存储空间。它们能够存储的值的范畴从−2(N−1)到2(N−1)−1,其中N是存储空间的位数。
整数类型有可选的UNSIGNED属性,示意不容许负值,这大抵能够使负数的下限进步一倍。例如TINYINT UNSIGNED能够存储的范畴是0~255,而TINYINT的存储范畴是−128~127。有符号和无符号类型应用雷同的存储空间,并具备雷同的性能,因而能够依据理论状况抉择适合的类型。
注:IP地址实际上是32位无符号整数,应该用INT存储,MySQL提供INETATON和INETNTOA两个转换IP地址的函数。
实数类型
实数是带有小数局部的数字。然而,它们不只是为了存储小数局部,也能够应用DECIMAL存储比BIGINT还大的整数。MySQL既反对准确类型,也反对不准确类型。FLOAT和DOUBLE类型反对应用规范的浮点运算进行近似计算,如果须要晓得浮点运算是怎么计算的,则须要研究所应用的平台的浮点数的具体实现。DECIMAL类型用于存储准确的小数,在MySQL 5.0和更高版本,DECIMAL类型反对准确计算。
浮点和DECIMAL类型都能够指定精度。对于DECIMAL列,能够指定小数点前后所容许的最大位数,这会影响列的空间耗费,MySQL 5.0和更高版本将数字打包保留到一个二进制字符串中(每4个字节存9个数字)。例如,DECIMAL(18,9)小数点两边将各存储9个数字,一共应用9个字节:小数点前的数字用4个字节,小数点后的数字用4个字节,小数点自身占1个字节。
浮点类型在存储同样范畴的值时,通常比DECIMAL应用更少的空间。FLOAT应用4个字节存储。DOUBLE占用8个字节,相比FLOAT有更高的精度和更大的范畴。
因为须要额定的空间和计算开销,所以应该尽量只在对小数进行准确计算时才应用DECIMAL——例如存储财务数据。但在数据量比拟大的时候,能够思考应用BIGINT代替DECIMAL,将须要存储的货币单位依据小数的位数乘以相应的倍数即可,这样能够同时防止浮点存储计算不准确和DECIMAL准确计算代价高的问题。
字符串类型
VARCHAR和CHAR类型
因为当初基本上所有的MySQL数据库应用的都是InnoDB存储引擎,而且应用的字符集都是utf8或者utf8mb4这样的多字节字符集。在这种状况下,varchar和char类型都须要应用1~2个额定字节去存储字符串的长度,此时char相比varchar曾经不具备任何的劣势,所以举荐所有的字符串类型都应用varchar。
BLOB和TEXT类型
BLOB和TEXT都是为存储很大的数据而设计的字符串数据类型,别离采纳二进制和字符形式存储。
MySQL对BLOB和TEXT列进行排序与其余类型是不同的:它只对每个列的最前max_sort_length字节而不是整个字符串做排序。如果只须要排序后面一小部分字符,则能够减小max_sort_length的配置,或者应用ORDER BY SUSTRING(column,length)。
MySQL不能将BLOB和TEXT列全副长度的字符串进行索引,也不能应用这些索引打消排序。同时因为Memory引擎不反对BLOB和TEXT类型,所以,如果查问应用了BLOB或TEXT列并且须要应用隐式长期表,将不得不应用磁盘长期表。
最好的解决方案是尽量避免应用BLOB和TEXT类型。如果切实无奈防止,有一个技巧是在所有用到BLOB字段的中央都应用SUBSTRING(column,length)将列值转换为字符串(在ORDER BY子句中也实用),这样就能够应用内存长期表了。然而要确保截取的子字符串足够短,不会使长期表的大小超过max_heap_table_size或tmp_table_size,超过当前MySQL会将内存长期表转换为MyISAM磁盘长期表。
日期和工夫类型
DATETIME类型能保留1001年到9999年范畴的值,精度为秒,与时区无关,应用8个字节的存储空间。TIMESTAAMP类型保留了格林尼治规范工夫以来的秒数,只能示意1970年到2038年范畴的值,显示的值依赖时区,应用4个字节的存储空间。
通常应该尽量应用TIMESTAMP,因为它比DATETIME空间效率更高。
注:MySQL5.6.4版本开始反对比秒更小的存储粒度,格局为 工夫类型(如timestamp)(n),n最大为6。
标识列数据类型抉择
整数通常是标识列最好的抉择,因为它们很快并且能够应用AUTO_INCREMENT。
如果可能,应该防止应用字符串类型作为标识列,例如MD5()、SHA1()或者UUID()产生的字符串。因为它们很耗费空间,并且通常比数字类型慢。这些函数生成的新值会任意散布在很大的空间内,这会导致INSERT以及一些SELECT语句变得很慢:
- 因为插入值会随机地写到索引的不同地位,所以使得INSERT语句更慢。这会导致页决裂、磁盘随机拜访,以及对于聚簇存储引擎产生聚簇索引碎片。
- SELECT语句会变得更慢,因为逻辑上相邻的行会散布在磁盘和内存的不同中央。
- 随机值导致缓存对所有类型的查问语句成果都很差,因为会使得缓存赖以工作的拜访局部性原理生效。如果整个数据集都一样的“热”,那么缓存任何一部分特定数据到内存都没有益处;如果工作集比内存大,缓存将会有很多刷新和不命中。
不好的Schema设计实际
过多的列
MySQL的存储引擎API工作时须要在服务器层和存储引擎层之间通过行缓冲格局拷贝数据,而后在服务器层将缓冲内容解码成各个列。从行缓冲中将编码过的列转换成行数据结构的操作代价是十分高的,转换的代价依赖于列的数量。
过多的关联
如果查问中存在过多的关联,那么解析和优化查问的代价会成为MySQL的问题。一个粗略的教训法令,如果心愿查问执行得疾速且并发性好,单个查问最好在12个表以内做关联。
适度应用ENUM
如果列的值可能会在当前裁减,那么就应该防止应用ENUM类型。在MySQL 5.1和更新版本中,如果不是在列表的开端增加值会须要ALTER TABLE,对于大表来说会导致重大的性能问题。
范式和反范式
范式的长处:
- 范式化的更新操作通常比反范式化要快。
- 当数据较好地范式化时,就只有很少或者没有反复数据,所以只须要批改更少的数据。
- 范式化的表通常更小,能够更好地放在内存里,所以执行操作会更快。
- 很少有多余的数据意味着检索列表数据时更少须要DISTINCT或者GROUP BY语句。
范式的毛病:
- 通常须要关联。
- 范式化可能将列寄存在不同的表中,这样会使某些索引生效。
混用范式化和反范式化
范式化和反范式化的schema各有优劣,怎么抉择最佳的设计?事实是,在理论利用中常常须要混用,可能应用局部范式化的schema、缓存表,以及其余技巧。最常见的反范式化数据的办法是复制或者缓存,在不同的表中存储雷同的特定列。
在某些须要特定的查问条件和排序的状况下,能够在父表中冗余一些字段到子表。例如有user表和message表,要查问付费用户最近10条数据,齐全范式化查问的效率较低下,能够在message表中冗余账户类型的字段并建设好索引,这将十分高效。不过更新账户类型的时候须要更新两张表。这时须要思考更新的频率及时长,来和查问的频率作比拟,而后做出取舍。
缓存衍生值也是有用的。如果须要显示每个用户发了多少音讯(像很多论坛做的),能够每次执行一个低廉的子查问来计算并显示它,也能够在user表中建一个num_messages列,每当用户发新音讯时更新这个值。
延长浏览:
对关系型数据库五个范式的了解
如何了解关系型数据库的常见设计范式?
缓存表和汇总表
有时晋升性能最好的办法是在同一张表中保留衍生的冗余数据。然而,有时也须要创立一张齐全独立的汇总表或缓存表(特地是为满足检索的需要时)。如果能答应大量的脏数据,这是十分好的办法,然而有时的确没有抉择的余地(例如,须要防止简单、低廉的实时更新操作)。
术语“缓存表”和“汇总表”没有规范的含意。咱们用术语“缓存表”来示意存储那些能够比较简单地从schema其余表获取(然而每次获取的速度比较慢)数据的表(例如,逻辑上冗余的数据)。而术语“汇总表”时,则保留的是应用GROUP BY语句聚合数据的表(例如,数据不是逻辑上冗余的)。也有人应用术语“累积表(Roll-Up Table)”称说这些表。因为这些数据被“累积”了。
以网站为例,假如须要计算之前24小时内发送的音讯数。在一个很忙碌的网站不可能保护一个实时准确的计数器。作为代替计划,能够每小时生成一张汇总表。这样兴许一条简略的查问就能够做到,并且比实时保护计数器要高效得多。毛病是计数器并不是100%准确。
如果必须取得过来24小时精确的音讯发送数量(没有脱漏),有另外一种抉择。以每小时汇总表为根底,把前23个残缺的小时的统计表中的计数全副加起来,最初再加上开始阶段和完结阶段不残缺的小时内的计数。
当然,更好的办法是应用内存数据库来实现这个计数器,例如Redis。