mysql支持的数据类型很是多,正确选择的数据类型对于得到高性能相当重要。无论存储哪一种类型的数据,下面几个简单的原则都有助于作出更好的选择:mysql
更小一般更好,尽可能使用能够正确存储数据的最小数据类型,更小的数据类型一般更快,由于它们占用更少的磁盘、内存和CPU缓存,而且处理时须要的CPU周期也更少sql
简单就好,简单数据类型的操做一般须要更少的CPU周期。例如,整型比字符操做代价更低,由于字符集和校对规则(排序规则)使字符比较比整型比较更复杂。使用mysql内建的类型(data、time、datatime)而不是字符串来存储日期和时间,用整型存储IP地址数据库
尽可能避免NULL,最好指定列为NOT NULL,除非真的须要存储NULL值。若是查询中包含可为NULL的列,对mysql来讲更难优化,由于可为NULL的列使得索引、索引统计和值比较都更复杂。可为NULL的列会使用更多的存储空间,在mysql中也须要特殊处理。当可为NULL的列被索引时,每一个索引记录须要一个额外的字节。InnoDB使用单独的位(bit)存储NULL值。缓存
有两种类型的数字:整数和实数。若是存储整数,可使用这几种整数类型:tinyint、smallint、mediumint、int、bigint,分别使用:八、1六、2四、3二、64位存储空间,它们能够存储值的范围从-2^(N-1)到(2^(N-1))-1,其中N是存储空间的位数。服务器
整数类型有可选的unsigned属性,表示不容许负值,这大体可使正数的上限提升一倍,例如无符号型tinyint能够存储的范围是0~255,而tinyint的存储范围是-128~127。有符号和无符号类型使用相同的存储空间,并具备相同的性能,所以能够根据实际状况选择合适的类型。数据结构
mysql能够为整数类型提供宽度,如int(11),对大多数应用这是没有意义的:它不会限制值的合法范围,只是规定了mysql的一些交互工具用来显示字符的个数。对于存储和计算来讲,int(1)和int(20)相同。并发
实数是带有小数部分的数字,然而,它们不仅是为了存储小数部分,也可使用decimal存储比bigint还大的整数。mysql既支持精确类型,也支持不精确类型:ide
float和double类型支持使用标准的浮点运算进行近似计算 decimal类型用于存储精确的小数 浮点和decimal类型均可以指定精度,对于decimal列,能够指定小数点先后所容许的最大位数,这会影响列的空间消耗。mysql 5.0和更高版本将数字打包保存到一个二进制字符串中(每4个字节存9个数字)。例如,decimal(18,9)小数点两边将各存储9个数字,一共使用9个字节:小数点前的数字用4个字节,小数点后的数字用4个字节,小数点自己占1个字节。mysql 5.0和更高版本中的decimal类型容许最多65个数字。函数
浮点类型在存储一样范围的值时,一般比decimal使用更少的空间。float使用4个字节存储,double占用8个字节,相比float有更高的精度和更大的范围。和整数类型同样,能选择的只是存储类型;mysql使用double做为内部浮点计算的类型。工具
由于须要额外的空间和计算开销,因此应该尽可能只在对小数进行精确计算时才使用decimal——例如存储财务数据。但在数据量比较大的时候,能够考虑使用bigint代替decimal,将须要存储的货币单位根据小数的位数乘以相应的倍数便可,这样能够同时避免浮点存储计算不精确和decimal精确计算代价高的问题。
mysql容许使用非标准语法:float(m,d)、real(m,d)或double(m,d),这里(m,d)表示该值一共保存m位数字,其中d位数字在小数点后面。例如,定义为float(7,4)的列保存值的范围:-999.9999~999.9999,在实际保存值时会四舍五入,若是在float(7,4)列内插入999.00009,实际保存值999.0001。
decimal和numeric在mysql中视为相同的类型,它们用于保存精确值,例如财务数据。当定义列为该类型时,能够指定精度和标度,例如,decimal(5,2)中5是精度,2是标度,精度表示能够保存数字的总位数,标度表示小数点后能够保存数字的位数。
mysql支持多种字符串类型,每种类型还有不少变种。
varchar和char varchar和char是两种最主要的字符串类型。
varchar类型用于存储可变长字符串,是最多见的字符串数据类型,它比定长类型更节省空间,由于它仅使用必要的空间(例如,越短的字符串使用越少的空间)。在mysql 5.0或更高版本,存储和检索varchar时会保留末尾空格。varchar须要使用1或2个额外字节记录字符串的长度:若是列的最大长度小于或等于255字节,需额外使用1个字节,不然使用2个字节。 下面这些状况下使用varchar是合适的:
字符串列的最大长度比平均长度大不少
列的更新不多,因此碎片不是问题
使用了像utf-8这样复杂的字符集,每一个字符都使用不一样的字节数进行存储 char类型是定长的:mysql老是根据定义的字符串长度分配足够的空间。当存储char值时,mysql会删除全部的末尾空格。char适合存储很短的字符串,或者全部值都接近同一个长度:
char很是适合存储密码的md5值,由于这是一个定长的值
对于常常变动的数据,char也比varchar更好,由于定长的char类型不容易产生碎片
对于很是短的列,char也比varchar在存储空间上也更有效率
VARCHAR(5)和VARCHAR(200)存储'hello'的空间开销是同样的.更长的列会消耗更多的内存,应为mysql一般会分配固定大小的内存块来保存内部值.尤为是使用内存临时表进行排序或操做时会特别糟糕.再利用磁盘临时表进行排序时也一样糟糕.因此最好的策略是只分配真正须要的空间.
varbinary和binary
varbinary和binary类型存储的是二进制字符串。二进制字符串和常规字符串很是类似,可是二进制字符串存储的是字节码而不是字符,填充也不同:mysql填充binary采用的是\0(零字节)而不是空格,在检索时也不会去掉填充值。
当须要存储二进制数据,而且但愿mysql使用字节码而不是字符进行比较时,这些类型是很是有用的。二进制比较的优点并不只仅体如今大小写敏感上。mysql比较binary字符串时,每次按一个字节,而且根据该字节的数值进行比较。所以,二进制比较比字符比较简单不少,因此也就更快。
blob和text
blob和text都是为存储很大的数据而设计的字符串数据类型,分别采用二进制和字符方式存储。实际上,它们分别属于两组不一样的数据类型家族:字符类型是tinytext、text、mediumtext、longtext;对应的二进制类型是tinyblob、blob、mediumblob、longblob。
与其它类型不一样,mysql把每一个blob和text值看成一个独立的对象处理。存储引擎在存储时一般会作特殊处理。当blob和text值太大时,InnoDB会使用专门的“外部”存储区域来进行存储,此时每一个值在行内须要1~4个字节存储一个指针,而后在外部存储区域存储实际的值。
blob和text家族之间仅有的不一样是blob类型存储的是二进制数据,没有排序规则或字符集,而text类型有字符集和排序规则。
mysql对blob和text列进行排序与其余类型是不一样的:它只对每一个列的最前max_sort_length字节而不是整个字符串作排序。
mysql不能将blob和text列所有长度的字符串进行索引,也不能使用这些索引消除排序。
mysql能存储的最小时间粒度为秒,提供两种类似的日期时间类型:datetime和timestamp,提供日期类型:date,提供时间类型:time。
datetime类型能保存大范围的值,从1001年到9999年,精度为妙,使用8个字节的存储空间。
timestamp类型保存了从1970年1月1日午夜(格林尼治标准时间)以来的秒数,它和UNIX时间戳相同,使用4个字节的存储空间,所以它的范围比datetime小的多:只能表示从1970年到2038年。timestamp显示的值也依赖于时区,mysql服务器、操做系统,以及客户端链接都有时区设置。
默认状况下,若是插入时没有制定第一个TIMESTAMP列的值,mysql则设置这个列的值为当前时间.再插入一行记录时,mysql默认也会更新第一个TIMESTAMP列的值.TIMESTAMP列默认为NOT NULL.
mysql没有提供合适的数据类型存储比秒更小粒度的日期和时间,可使用BIGINT类型存储微秒级别的时间戳,或者用double存储以后的小数部分.
BIT 在mysql5.0以前BIT和TINYINT是同义词.5.0以及更新版本mysql把BIT当作字符串.最大长度为64位
为标识列(identifier column)选择合适的数据类型很是重要。一旦选定了一种类型,要确保在全部关联表中都使用一样的类型,类型之间须要精确匹配。在能够知足值的范围的需求,而且预留将来增加空间的前提下,应该选择最小的数据类型:
整数一般是标识列最好的选择,由于它们很快而且可使用AUTO_INCREMENT 尽可能避免使用字符串类型做为标识符,由于它们很消耗空间,而且一般比数字类型慢。对于彻底随机的字符串也须要多加注意,随机产生的值会任意分布在很大的空间内,这会致使insert以及一些select语句变的很慢
插入值会随机地写到索引的不一样位置
select语句会变得更慢,由于逻辑上相邻的行为会分布在磁盘和内存的不一样地方
随机值致使缓存对全部类型的查询语句效果都不好.
常用varchar(15)列来存储IP地址,然而,它们其实是32位无符号整数,不是字符串,用小数点将地址分红四段的表示方法只是为了阅读。因此应该用无符号整数存储IP地址,mysql提供INET_ATON()、INET_NTOA()函数在这两种表示方法之间转换,示例以下。
`ip` int(10) unsigned DEFAULT '0'; UPDATE tb_test SET ip = INET_ATON('192.168.1.1'); SELECT INET_NTOA(ip) FROM tb_test;
在mysql特定实现下,设计schema时须要避免的错误:
太多的列
mysql的存储引擎API工做时须要在服务器层和存储引擎层之间经过行缓冲格式拷贝数据,而后在服务器层将缓冲内容解码成各个列。从行缓冲中将编码过的列转换成行数据结构的操做代价是很是高的。InnoDB的行结构老是须要转换,转换的代价依赖于列的数量,若是计划使用数千个字段,必须意识到服务器的性能运行特征会有一些不一样
太多的关联
mysql限制了每一个关联操做最多只能有61张表。一个粗略的经验法则,若是但愿查询执行的快速且并发性好,单个查询最好在12个表之内作关联
全能的枚举
注意防止过分使用ENUM
变相的枚举
非此发明的NULL
对于任何给定的数据一般都有不少种表示方法,从彻底的范式化到彻底的反范式化,以及二者的折中。在范式化的数据库中,每一个事实数据会出现而且只出现一次。相反,在反范式化的数据库中,信息是冗余的,可能会存储在多个地方。
由于性能问题而寻求帮助时,常常会被建议对schema进行范式化设计,尤为是写密集的场景:
范式化的更新操做一般比反范式化要快
当数据较好的范式化时,就只有不多或者没有重复数据,因此只须要修改更少的数据
范式化的表一般更小,能够更好的放在内存里,因此执行操做会更快
不多有多余的数据意味着检索列表数据时更少须要DISTINCT或者GROUP BY语句
范式化设计的schema的缺点是一般须要关联。稍微复杂一些的查询语句在符合范式化的schema上均可能须要至少一次关联,也许更多,这不但代价昂贵,也可能使一些索引策略无效。
反范式化的schema由于全部数据都在一张表中,能够很好的避免关联。若是不须要关联表,则对大部分查询最差的状况——即便表没有使用索引——是全表扫描。当数据比内存大时这可能比关联要快的多,由于这样避免了随机I/O。
事实是,彻底的范式化和彻底的反范式化schema都是实验室里才有的东西,在实际应用中常常须要混用,可能使用部分范式化的schema、缓存表,以及其余技巧。
最多见的反范式化数据的方法是复制或者缓存,在不一样的表中存储相同的特定列。在mysql 5.0和更新版本中,可使用触发器更新缓存值,这使得实现这样的方案变的更简单。
有时提高性能最好的方法是在同一张表中保存衍生的冗余数据。然而,有时也须要建立一张彻底独立的汇总表或缓存表(特别是为知足检索的需求时)。若是能允许少许的脏数据,这是很是好的方法,可是有时确实没有选择的余地(例如,须要避免复杂、昂贵的实时更新操做)。
术语“缓存表”和“汇总表”没有标准的含义。咱们用术语“缓存表”来表示存储那些能够比较简单地从schema 其余表获取(可是每次获取的速度比较慢)数据的表(例如,逻辑上冗余的数据)。而术语“汇总表”时,则保存的是使用GROUP BY语句聚合数据的表(例如,数据不是逻辑上冗余的)。也有人使用术语“累积表(Roll-Up Tables)”称呼这些表。由于这些数据被“累积”了。
仍然以网站为例,假设须要计算以前24 小时内发送的消息数。在一个很繁忙的网站不可能维护一个实时精确的计数器。做为替代方案,能够每小时生成一张汇总表。这样也许一条简单的查询就能够作到,而且比实时维护计数器要高效得多。缺点是计数器并非100% 精确。
若是必须得到过去24 小时准确的消息发送数量(没有遗漏),有另一种选择。以每小时汇总表为基础,把前23 个完整的小时的统计表中的计数所有加起来,最后再加上开始阶段和结束阶段不完整的小时内的计数。
假设统计表叫做msg_per_hr 而且这样定义:
CREATE TABLE msg_per_hr ( hr DATETIME NOT NULL, cnt INT UNSIGNED NOT NULL, PRIMARY KEY(hr) );
能够经过把下面的三个语句的结果加起来,获得过去24 小时发送消息的总数。咱们使用LEFT(NOW(),14) 来得到当前的日期和时间最接近的小时:
mysql> SELECT SUM(cnt) FROM msg_per_hr -> WHERE hr BETWEEN -> CONCAT(LEFT(NOW(), 14), '00:00') - INTERVAL 23 HOUR -> AND CONCAT(LEFT(NOW(), 14), '00:00') - INTERVAL 1 HOUR; mysql> SELECT COUNT() FROM message -> WHERE posted >= NOW() - INTERVAL 24 HOUR -> AND posted < CONCAT(LEFT(NOW(), 14), '00:00') - INTERVAL 23 HOUR; mysql> SELECT COUNT() FROM message -> WHERE posted >= CONCAT(LEFT(NOW(), 14), '00:00');
无论是哪一种方法——不严格的计数或经过小范围查询填满间隙的严格计数——都比计算message 表的全部行要有效得多。这是创建汇总表的最关键缘由。实时计算统计值是很昂贵的操做,由于要么须要扫描表中的大部分数据,要么查询语句只能在某些特定的索引上才能有效运行,而这类特定索引通常会对UPDATE 操做有影响,因此通常不但愿建立这样的索引。计算最活跃的用户或者最多见的“标签”是这种操做的典型例子。缓存表则相反,其对优化搜索和检索查询语句颇有效。这些查询语句常常须要特殊的表和索引结构,跟普通OLTP 操做用的表有些区别。
例如,可能会须要不少不一样的索引组合来加速各类类型的查询。这些矛盾的需求有时须要建立一张只包含主表中部分列的缓存表。一个有用的技巧是对缓存表使用不一样的存储引擎。例如,若是主表使用InnoDB,用MyISAM 做为缓存表的引擎将会获得更小的索引占用空间,而且能够作全文搜索。有时甚至想把整个表导出MySQL,插入到专门的搜索系统中得到更高的搜索效率,例如Lucene 或者Sphinx 搜索引擎。
在使用缓存表和汇总表时,必须决定是实时维护数据仍是按期重建。哪一个更好依赖于应用程序,可是按期重建并不仅是节省资源,也能够保持表不会有不少碎片,以及有彻底顺序组织的索引(这会更加高效)。
当重建汇总表和缓存表时,一般须要保证数据在操做时依然可用。这就须要经过使用“影子表”来实现, “ 影子表”指的是一张在真实表“背后”建立的表。当完成了建表操做后,能够经过一个原子的重命名操做切换影子表和原表。例如,若是须要重建 my_summary,则能够先建立 my_summary_new,而后填充好数据,最后和真实表作切换:
mysql> DROP TABLE IF EXISTS my_summary_new, my_summary_old; mysql> CREATE TABLE my_summary_new LIKE my_summary; -- populate my_summary_new as desired mysql> RENAME TABLE my_summary TO my_summary_old, my_summary_new TO my_summary;
许多数据库管理系统(例如Oracle 或者微软SQL Server)都提供了一个被称做物化视图的功能。物化视图其实是预先计算而且存储在磁盘上的表,能够经过各类各样的策略刷新和更新。MySQL 并不原生支持物化视图。
Flexviews 比彻底本身实现的解决方案要更精细,而且提供了不少不错的功能使得能够更简单地建立和维护物化视图。它由下面这些部分组成:
变动数据抓取(Change Data Capture,y CDC)功能,能够读取服务器的二进制日志而且解析相关行的变动。
一系列能够帮助建立和管理视图的定义的存储过程。
一些能够应用变动到数据库中的物化视图的工具。
对比传统的维护汇总表和缓存表的方法,Flexviews 经过提取对源表的更改,能够增量地从新计算物化视图的内容。这意味着不须要经过查询原始数据来更新视图。
先写出一个SELECT 语句描述想从已经存在的数据库中获得的数据。这可能包含关联和聚合(GROUP BY)。Flexviews 中有一个辅助工具能够转换SQL 语句到Flexviews 的API 调用。Flexviews 会作完全部的脏活、累活:监控数据库的变动而且转换后用于更新存储物化视图的表。如今应用能够简单地查询物化视图来替代查询须要检索的表。
应用在表中保存计数器,则在更新计数器时可能碰到并发问题。建立一张独立的表存储计数器一般是个好主意,这样可以使计数器表小且快。使用独立的表能够帮助避免查询缓存失效。
假设有一个计数器表,只有一行数据,记录网站的点击次数:
CREATE TABLE hit_counter( cnt int unsigned not null ) ENGINE=InnoDB;
网站的每次点击都会致使对计数器进行更新:
UPDATE hit_counter set cnt = cnt + 1;
问题在于,对于任何想要更新这一行的事务来讲,这条记录上都有一个全局的互斥锁(mutex)。这会使得这些事务只能串行执行。要得到更高的并发更新性能,也能够将计数器保存在多行中,每次随机选择一行进行更新。这样作须要对计数器表进行以下修改:
CREATE TABLE hit_counter( slot tinyint unsigned not null primary key, cnt int unsigned not null ) ENGINE=InnoDB;
而后预先在这张表增长100 行数据。如今选择一个随机的槽(slot)进行更新:
UPDATE hit_counter set cnt = cnt + 1 where slot = RAND() * 100;
要得到统计结果,须要使用下面这样的聚合查询:
SELECT SUM(cnt) FROM hit_counter;
一个常见的需求是每隔一段时间开始一个新的计数器。若是须要这么作,则能够简单的修改一下表设计:
CREATE TABLE daily_hit_counter( day date not null, slot tinyint unsigned not null, cnt int unsigned not null, primary key(day,slot) ) ENGINE=InnoDB;
若是但愿减小表的行数,以免表变得太大,能够写一个周期执行的任务,合并全部结果到0 号槽,而且删除全部其余的槽:
mysql> UPDATE daily_hit_counter as c -> INNER JOIN ( -> SELECT day, SUM(cnt) AS cnt, MIN(slot) AS mslot -> FROM daily_hit_counter -> GROUP BY day -> ) AS x USING(day) -> SET c.cnt = IF(c.slot = x.mslot, x.cnt, 0), -> c.slot = IF(c.slot = x.mslot, 0, c.slot); mysql> DELETE FROM daily_hit_counter WHERE slot <> 0 AND cnt = 0;
总结:
尽可能避免过分设计
使用小而简单的合适数据类型,除非真实数据模型中有确切的须要,不然应该尽量地避免使用NULL值。
尽可能使用相同的数据类型存储类似或相关的值,尤为是要在关联条件中使用的列。
注意可变长字符串,其在临时表和排序时可能致使悲观的按最大长度分配内存。
尽可能使用整型定义标识列。
避免使用MySQL已经遗弃的特性。
当心使用ENUM和SET。