4.Schema与数据类型优化
4.1 选择优化的类型
原则:
1.更小的通常更好
一般情况下,应该尽量选择可以正确存储数据的最小数据类型。更小的数据类型通常更快,因为它们占用更少的磁盘,内存
和 cpu 缓存,并且处理时需要 cpu 周期也更少。
但是要确保没有低估需要存储的值的范围。
2.简单就好
简单的数据类型的操作通常需要更少的 cpu 周期。例如,整型比字符串操作代价更低,因为字符集和校对规则(排序规则)使
字符集比整型复杂。这里有2个例子,一个是应该使用 mysql 内建的类型而不是字符串来存储日期和时间,另外一个是应该使用
整型来存储 IP 地址。
3.尽量避免 NULL
很多表都可以包含为 null 的列,即使应用程序并不需要保持 null 也是如此,这是因为 null 是列的默认属性。通常情况下,
最好指定列为 not null。
如果查询中包含可为 null 的值,对 mysql 来说更难优化,因为可为 null 的列使得索引,索引统计和值都变的复杂。可为null
的列会使用更多的存储空间,在 mysql 里面也需要特殊处理。当可为 null 的列被索引时,每个索引记录需要一个额外的字节,在
MyISAM 里面甚至还可能导致固定大小的索引编程可变大小的索引.
通常把可为 null的列改为 not null 带来的性能提升比较小,所以(调优时)没有必要首先在现有的 schema 中查找并修改掉这种情况。
当然也有例外,例如值得一提的是,InnoDB 使用单独的位(bit)存储 null值,所以对于稀疏数据有更好的空间效率。
4.1.1 整数类型
类型:tinyint(8),samllint(16),mediumint(24),int(32),bigint(64)
可选的属性 unsigned 属性,表示不允许负值。
mysql 可以为整数类型指定宽度,例如 int(11)。对存储来说,int(1)和 int(11)是一样的。
4.1.2 实数类型
实数是带小数部分的数字。然后,它们不只是为了存储小数部分,也可以使用 decimal 存储比 bigint 还大的整数。
float 和 double 类型都支持使用标准的浮点进行近似的计算。
decimal 用于存储精确的小数。
因为 cpu 不支持对 decimal 的直接计算,所以在 mysql 5.0 以及更高的版本中,mysql 服务器自身实现了decimal 的高精度计算。
相对而言,cpu 直接支持原生浮点运算,所以浮点运算明显更快。
浮点和 decimal 都可以指定精度。对于 decimal 而言,可以指定小数点前后所允许的最大位数。这会影响列的空间消耗。mysql 5.0 和
更高版本将数字打包保存到一个二进制字符中(每4个字节存9个数字)。例如,decimal(18,9) 小数两边各存储9个数字,一共使用9个字节:小数
前的数字用4个字节,小数后的数字用4个字节,小数本身占用一个字节.
DECIMAL(P,D);
P是表示有效数字数的精度。 P范围为1〜65。
D是表示小数点后的位数。 D的范围是0~30。MySQL要求D小于或等于(<=)P。
DECIMAL(P,D)表示列可以存储D位小数的P位数。十进制列的实际范围取决于精度和刻度。
例如,DECIMAL(19,9)对于小数部分具有9位数字,对于整数部分具有19位= 10位数字,小数部分需要4个字节。 整数部分对于前9位数字需要4个字节,1个剩余字节需要1个字节。DECIMAL(19,9)列总共需要9个字节。
mysql 5.0 和更高的版本中的 decimal类型允许最多65个数字。而早起的mysql 版本中这个限制是 254个数字,并且保存为未压缩的字符串(每个
数字一个字节)。然后,这些版本实际上并不能在计算中使用这么大的数字,因为 decimal 只是一种存储格式,在计算中 decimal 会转换为 double 类型。
浮点数在存储同样范围的值时,通常比 decimal 使用更少的空间。float 使用4个字节,double 使用8个字节,相比 float 有更高的精度和更大的范围。
和整数类型一样,能选择的只是存储类型;mysql 使用 double 作为内部浮点计算的类型。
因为需要额外的空间和计算开销,所有应该尽量只在对小数进行精确计算时才使用 decimal --- 例如存储财务数据。但在数据较大时,可以考虑使用 bigint
替代 decimal,将需要存储的货币单位根据小数的位数乘以相应的倍数即可。这样可以同时避免浮点存储计算不精确和 decimal 精确计算代价高的问题。
float和double都是浮点型,而decimal是定点型;
MySQL 浮点型和定点型可以用类型名称后加(M,D)来表示,M表示该值的总共长度,D表示小数点后面的长度,M和D又称为精度和标度,如float(7,4)的 可显示为-999.9999,MySQL保存值时进行四舍五入,如果插入999.00009,则结果为999.0001。
FLOAT和DOUBLE在不指 定精度时,默认会按照实际的精度来显示,而DECIMAL在不指定精度时,默认整数为10,小数为0。
超过精度时,浮点数不会警告,定点数会警告。
4.1.3 字符串类型
从 mysql 4.1 开始,每个字符串可以定义自己的字符集和排序规则,或者说校对规则。这些东西很大程度上影响性能。
varchar 和 char 类型:
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 会删除末尾的空格。char 值会根据需要采用空格进行填充以方便比较。
char 很适合存储很短的字符串,或者所有的值都接近一个长度。例如, char 非常适合存储密码的 md5 值,因为这是一个定长的值。对于经常变更的数据,char 也比varchar
好,因为定长的 char 不容易产生碎片。对于非常短的列,char 比 varchar 在存储空间上也更有效率。例如,使用 char(1) 存储只有 Y 和 N 的值,如果采用单单字节字符集
只需要一个字节,但是 varchar(1)却需要2个字节,因为还有一个记录长度的额外字节。
记住,字符长度定义不是字节数,是字符数。多字节字符集会需要更多的空间存储单个字符。
数据如何存储取决于存储引起,并非所有的存储引起都会按照相同的方式处理定长和变长的字符串。memory 引擎只支持定长的行,即使有变长字段也会根据最大长度分配最大空间。
不过,填充和截取空格的行为在不同的存储引擎都是一样的,因为这是在mysql 服务器层进行处理的。
与 char 跟 varchar 类似的还有 binary 和 varbinary ,它们存储的是二进制字符串。二进制字符串跟常规字符串非常相似,但是二进制字符串存储的是字节码而不是字符。
blob 和 text 类型:
都是为存储很大的数据而设计的字符串数据类型,分别采用二进制和字符方式存储。
实际上,它们分别属于两组不同的数据类型家族:字符类型是 tinytext,smalltext,text,mediumtext,longtext;对应的二进制类型是 tinyblob,smallblob,blob,
mediumblob,longblob.blob 是 smallblob 的同义词,text是 smalltext 的同义词.
与其他类型不同,mysql 会把每个 blob 和 text 值当作一个独立的对象处理。存储引擎存储时通常会做特殊处理。当 blob 和 text 值太大时,InnoDB 会使用专门的
‘外部’存储区域来存储,此时每个值在行内需要1~4个字节存储一个指针,然后在外部存储区域存储实际的值。
blob 和 text 家族之间仅有的不同是 blob 类型存储的是二进制数据,没有排序规则或字符集,而 text 类型是有字符集和排序规则的。
mysql 对 blob 和 text列进行排序和其他类型是不同的:它只对每个列的最前 max_sort_length 字节而不是整个字符串做排序。如果只需要排序前面的一小部分字符,
则可以减少 max_sort_length 的配置,或者使用 order by substring(column, length);
mysql 不能将 blob 和 text 列全部长度的字符串进行索引,也不能使用这些索引消除排序。
磁盘临时表和文件排序:
因为 memory 不支持 blob 和 text 类型,所以,如果查询使用了 blob 或者 text 列并且需要使用隐式临时表,将不得不使用 MyISAM 磁盘临时表,即使只有几行数据
也是如此。这会导致严重的性能开销,即使配置了 mysql 将临时表存储在内存块设备上(RAM Disk),依然需要许多昂贵的系统调用。
最好的解决办法是尽量避免使用 blob 和 text 类型。如果实在无法避免,有一个技巧是所有用到 blob 字段的地方都使用 substring(column, length)将列值转换为字符串,
这样就可以使用内存临时表了。但是需要确保截取的子字符串足够短,不会使用临时表的大小超过 max_heap_table_size 或 tmp_table_size,超过以后 mysql 会将内存临时表
转换为 MyISAM 磁盘临时表。
最坏的情况下长度分配对于排序的时候也是一样的,所以这一招对于内存中创建大临时表和文件排序,以及在磁盘上创建大临时表和文件排序这2种情况是很有帮助的。
使用枚举类型(enum)替换字符串类型:
有时候可以使用枚举类型替换常用的字符串类型。枚举类型可以把一些不重复的字符串存储成一个预定义的集合。mysql 在存储枚举时非常紧凑,会根据列表值的数量压缩到一个或者
2个字节中。mysql 会在内部将每个值在列表中的位置保持为整数,并且在表的 .frm 文件中保存 '数字---字符串'映射关系的 '查找表'.
实际存储的是整数,而不是字符串。
select e + 0 from enum_test;
枚举字段是按照内部存储的整数而不是定义的字符串进行排序的。
一种绕过这种限制的方式按照需要的顺序来定义枚举列。另外也可以在查询中使用 field() 函数显示的指定排序顺序,但这样会导致 mysql 无法利用索引来消除排序。
select e from enum_test order by field(e, 'apple', 'dog', 'fish');
枚举最不好的地方是,字符串列表是固定的,添加或者删除字符串必须使用 alter table。因此,对于一些列未来可能会改变的字符串,使用枚举不是一个好主意,除非能
接受只在列表末尾添加元素,这样在 mysql 5.1 中就可以不用重建整个表来完成修改。
由于 mysql把枚举值保持为整数,并且必须进行查找才能转换为字符串,所以枚举列有一些开销。通常枚举列表都比较小,所以开销还是可以控制,但也不能保证一直如此。
在特定情况下,把 char/varchar 列与枚举进行关联的可能会比直接关联 char/varchar 列更慢。
4.1.4 日期和时间类型
mysql 能存储的最小时间粒度为秒。
2种日期类型:datetime 和 timestamp
datetime: 这个类型最大能保存的范围,从1001到9999年,精度为秒。它把日期和时间封装到格式为 YYYYMMDDHHMMSS 的整数中,与时区无关。使用8个字节的存储空间。
默认情况下,mysql 以一种可排序,无歧义的格式显示 datetime 值。
timestamp: 保存了从 1970年1月1日午夜以来的描述,它和unix时间戳相同.timestamp 只是用4个字节的存储空间。因此它的范围比datetime 小的多:只能表示从
1970年到2038年。mysql 提供了 from_unixtime() 函数把 unix 时间戳转换为日期,并提供了 unix_timestamp() 函数把日期转换为 unix 时间戳。
timestamp 的显示的值依赖于时区。datetime 则保留文件表示的日期与时间。
timestamp 也有datetime 没有的特殊属性。默认情况下,如果插入时没有指定第一个 timestamp 列的值,mysql 则设置这个列的值为当前的时间。
除了特殊行为之外,通常也应该尽量使用 timestamp,因为它比 datetime 空间效率更高。
如果需要存储比秒更小的粒度怎么办?可以使用 bigint 存储。
4.1.5 位数据类型
bit:
在 mysql 5.0 之前,bit是和 tinyint 的同义词。可以使用 bit 列在一列中存储一个或者多个 true/false的值。bit(1)定义一个包含单个位的字段,bit(2)存储2个位。
bit 列最大的长度是64个位。
mysql 把 bit 当作字符串类型,而不是整数类型。当检索bit(1)的值时,结果是一个包含二进制0或者1的字符串,而不是 ASCII 码的 '0' 或者 '1'。
set:
如果需要保存很多true/false 值,可以考虑合并这些列到一个 set 数据类型,它在mysql内部是以一些列打包的位的集合来表示的。这样就能有效的利用了存储空间,并且
mysql 有像 find_in_set() 和 field() 这样的函数,方便查询。它的主要缺点是改变列的定义的代价比较高,需要 alter table,这对大表来说是非常昂贵的操作。
4.1.6 选择标识符
当选择标识列的类型时,不仅仅需要考虑存储类型,还需要考虑 mysql 对这种类型怎么执行计算和比较。比如,mysql 在内部使用整数存储 enum 和 set 类型,然后再比较
操作时转换为字符串。
一旦选定了一种类型,要确保在所有关联表中都使用相同的类型。类型之间需要精确匹配,包括 unsigned这样的属性。混合使用不同数据类型可能导致性能问题,即使没有性能问题
,在比较操作时隐式转换也可能导致很难发现的错误。
在可以满足值的范围要求下,并且预留未来增长空间的前提下,应该选择最小的数据类型。
整数类型:
整数类型通常是最好的标识符选择,因为它们很快且可以使用 auto_increment;
enum 和 set 类型:
enum,set 适合存储固定的信息,比如有序的状态,产品的类型,人的性别等。
字符串类型:
如果可能,应该避免使用字符串类型作为标识符,因为它们很耗空间,并且通常比数字类型慢。尤其是在 MyISAM 表里面使用字符串作为标识符时要小心,因为MyISAM 默认对
字符串使用压缩索引,这会导致查询变慢。
对于完全随机的字符串也需要多加注意,例如 md5(), sha1()或者 uuid()产生的字符串。这些函数生成的新值会任意分布在很大的空间内,这会导致 insert 以及一些
select 语句变的很慢。
1.因为插入值会随机的写到索引不同的位置,所以使得 insert 语句更慢。这会导致页分裂,磁盘随机访问,以及对于聚簇存储引擎产生聚簇索引碎片。
2.select 语句会变慢,因为逻辑上相邻的行会分布在磁盘和内存的不同地方。
3.随机值会导致对所有类型的查询语句效果都很差。因为会使得缓存赖以工作的访问局部原理失效。
uuid() 生成的值与加密散列函数例如 sha1()生成的值有不同的特征:uuid值虽然分布也不均匀,但还是有一定的顺序的。
4.1.7 特殊类型数据
IPv4 地址,任何经常使用 varchar(15)列来存储 ip 地址。然后,它实际上是32位无符号整数,不是字符串。mysql 提供了 inet_aton(),inet_ntoa()函数来转换。
4.2 MySQL schema 设计中的缺陷
1.太多的列
mysql 存储引擎api在工作时需要在服务器层和存储引擎层之间通过行缓冲格式拷贝数据,然后在服务器层将缓冲内容解码成各个列。从行缓冲中将编码过的列转换成
行数据结构的操作代价是非常高的。MyISAM 的定长行结构实际上是与服务器层的行结构正好匹配,所以不需要转化。然后,MyISAM的变长行结构和InnoDB的行结构则
总是需要转换。转换的代价依赖于列的数量。
2.太多的关联
所谓的 '实体-属性-值'(EAV) 设计模式是一个常见的糟糕的设计。mysql 限制了每个关联操作最多只能有61张表。单个查询最好在12个表以内做关联。
3.全能的枚举
在 mysql 中,当需要在枚举列表中增加一个新的时,就需要做一次 alter table 操作。
4.变相的枚举
5.非此发明的null
即使需要null时,可以用 0,空字符串,或者某个特殊值替代。
4.3 范式和反范式
4.3.1 范式的优点和缺点
1.范式话的更新操作通常比反范式快
2.当数据较好的范式化时,就只有很少或者没有重复数据,所以只需要修改更少的数据
3.范式化的表通常更小,可以更好的存放在内存中。
4.很少有多余的数据意味着检索表数据时更少需要 distinct 或者 group by 语句。
缺点是通常需要关联。
4.3.2 反范式的优点和缺点
反范式可以很好的避免关联。
如果不需要关联,则对大部分查询最差的情况---即使表没有使用索引---是全表扫描。当数据比内存大时这可能比关联要快的多,因为这样避免了随机IO.
4.3.3 混用范式和反范式
最常见的反范式数据的方法是复制或者缓存,在不同的表中存储相同的特定列。也可以使用触发器更新缓存值。
4.4 缓存表和汇总表
有时候提升性能最好的方法是在同一张表中保存衍生的冗余数据。然后,有时候也需要创建一张完全独立的汇总表或缓存表。
当重建汇总表和缓存表时,通常需要保证数据在操作时依然可用。这就需要通过使用 '影子表' 来实现,'影子表' 指的是在一张真实表的 '背后' 创建的表。
当完成建表操作后,可以通过一个原子重命名操作切换影子表和原表。
create table my_summary_new like my_summary;
...
rename table my_summary to my_summary_old, my_summary_new to my_summary;
4.4.1 物化视图
4.4.2 计数器表
如果应用在表中保存计数器,则在更新的时候看你碰到并发问题。
如果表只有一行记录,对于任何想要更新这一行的事务来说,这条记录上都有一个全局的互斥锁,使得这些事务只能串行执行。要获得更高的性能,也可以将计数器
保存在多行中,每次随机选择一行进行更新。
4.5 加快 alter table 操作的速度
alter table 操作的性能对大表来说是个大问题。mysql 执行大部分修改表结构的操作的方法是用新的结构创建一个空表,从旧表查出所有数据插入新表,然后删除旧表。
这样的操作困难需要花费很长的时间,如果内存不足而表又很大的话,而且还有很多索引的情况下尤其如此。
一般而言,大部分 alter table 操作将导致 mysql 服务中断。
对于常见的场景,使用的技巧有2种:
1.先在一台不提供服务的机器上执行 alter table 操作,然后和提供服务的主库进行切换;
2.另外一种是 '影子拷贝'。技巧是要求表结构创建一张和源表无关的新表,然后通过重命名和删除表的操作交换2张表。
不是所有的 alter table 都会引起表重建。
4.5.1 只修改 .frm 文件
1.创建一张具有相同结构的空表
2.执行 flush tables with read lock。这将会关闭所有正在使用的表,并且禁止任何表被打开
3.交换 .frm 文件
4.执行 unlock tables 来释放第二步的读锁
4.5.2 快速创建 MyISAM 索引
为了高效的载入数据到 MyISAM 中,有一个常用的技巧是先禁用索引,载入数据,然后重新启用索引。
alter table test.load_data disable keys;
...
alter table test.load_data enable keys;
不幸的是,这个办法对唯一索引无效。因为 disable keys 只对非唯一索引有效。MyISAM 会在内存中构建唯一索引,并且为载入的每一行检查唯一性。一旦索引的
大小超过了有效内存大小,载入操作就会变得越来越慢。
整形,如 int(11) // 11 指客户端显示的 **宽度**
decimal(M,D) // D只小数点后的位数,四舍五入的结果,M 为总位数,**精度**
varchar(n) //GBK编码,一个汉字占两个字节。UTF-16编码,通常汉字占两个字节,CJKV扩展B区、扩展C区、扩展D区中的汉字占四个字节(一般字符的Unicode范围是U+0000至U+FFFF,而这些扩展部分的范围大于U+20000,因而要用两个UTF-16)。UTF-8编码是变长编码,通常汉字占三个字节,扩展B区以后的汉字占四个字节。null 也会占用一个字节 **字节数**
datetime // 8字节
timestamp // 4字节,不设置这个字段,也会更新
选择数据类型方式:
1.更小的通常更好(更好的存储空间,更小的cpu)
2.简单就好(整数存储ip,)
3.避免null
varchar 与 char
varchar 省空间,但 update 会页分裂
blog 与 text
只针对前 max_sort_length 排序,
Memory 不支持这个2中类型,所以只能用磁盘临时表
枚举:
create table enum_test(
-> e enum('fish','apple','dog') not null);
insert into enum_test(e) values('fisha');
https://zhidao.baidu.com/question/454436588167256525.html
http://www.cnblogs.com/gomysql/p/3615897.html