Schema与数据类型优化

Posted forever_elf

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Schema与数据类型优化相关的知识,希望对你有一定的参考价值。

  选择数据类型  

    更小的通常更好:尽量使用可以正确存储数据的最小数据类型。因为它们占用的更少的磁盘,内存和CPU缓存,并且处理需要CPU周期也更少。

    简单就好:简单数据类型的操作通常需要更少的CPU周期。应该使用mysql内建的类型而不是字符串来存储日期和时间,用整形存储IP地址。

    尽量避免NULL:通常情况下,尽量指定列为NOT NULL。NNULL的列使索引,索引统计和值比较都比较复杂。当存在NULL的列被设置为索引时,每个索引记录需要额外的一个字节。InnoDB使用单独的位(bit)存储NULL值,所以对于稀疏数据有很好的空间效率。但这点不适合MyISAM。

 

 

  TIMESTAMP只使用DATETIME一半的存储空间,并且会根据时区变化,具有特殊的自动更新能力。TIMESTAMP允许时间范围要小很多。

 

  整数类型

    整数(whole number)和实数(real number)。如果存储整数,可以使用这几种整数类型:TINYINT,SMALLINT,MEDIUMINT,INT,BIGINT。分别使用8,16,32,64位存储空间,它们恶意存储的范围址从-2的N-1次方到2的N-1次方-1,N为存储空间的位数。

    整数类型有可选的UNSIGNED属性,表示不允许负值。

    整数计算一般使用64位的BIGINT整数,即使在32位的环境下也是如此。MySQL可以为整数类型指定宽度,它对于存储和计算来说是没有区别的,只是会限制一些交互工具显示字符的个数。

 

  实数类型

    实数是带有小数部分的数字。MySQL即支持精确类型,也支持不精确类型。

    FLOAT和DOUBLE类型支持使用标准的浮点运算进行近似计算,

    DECIMAL类型用于存储精确的小数。

    浮点和DECIMAL类型都可以指定精度。对于DECIMAL列,可以指定小数点前后所允许的最大位数,则会影响列的空间消耗。DECIMAL只是一种存储格式,在计算中DECIMAL会转换为DOUBLE类型。有很多种方法可以指定浮点列所需要的精度,这使得MySQL会选择不同的数据类型或在存储时对值进行取舍。这些精度定义是非标准的,所以建议只指定数据类型而不指定精度。

    浮点类型在存储同样范围的值时,通常比DECIMAL使用更少的空间。应尽量支队小树进行精确计算时才使用DECIMAL。在数据量比较大时,可以使用BIGINT代替DECIMAL。

 

 

  字符串类型

    VARCHAR和CHAR是两种最主要的字符串类型。它的存储和存储引擎的具体实现相关。

    VARCHAR类型用于存储可变字符串,是最常见的字符串数据类型,它比定长类型更节省空间。若MySQL表使用ROW_FORMAT=FIXED创建的话,每一行都会定长存储。VARCHAR需要使用1或2个额外字节记录字符串的长度:如果列的最大长度小于或等于255字节,则只使用1个字节表示,否则用两个。由于行是变长的,所以在UPDATE时可能使行变得比原来更长,需要额外的工作。若一个行占用的空间增长,并且在页内没有更多的空间可以存储,此时,MyISAM会将行拆成不同的片段进行存储,InnoDB或者额需要分裂页来使行放进页内。当字符串的最大长度比平均长度大很多时;列的更新很少;使用了UTF-8时比较适合用VARCHAR。MySQL在存储和检索时会保留末尾空格。InnoDB会把过长的VARCHAR存储为BLOB。

    CHAR类型时定长的:MySQL总是根据定义的字符串的长度分配足够的空间。当存储CHAR值时,MySQL会删除所有的末尾空格。CHAR值会根据需要采用空格进行填充以方便比较。CHAR适合存储很短的字符串,或者所有值都接近于一个长度。CHAR适合存储密码的MD5值,对于不经常变更的值CHAR也比较合适。对于比较短的列,CHAR比VARCHAR在存储空间上也更有效率。

    Memory引擎只支持定长的行,即使有变长字段也会根据最大长度分配最大空间。

    填充和截取空格的行为在不同存储引擎都是一样的,这是在MySQL服务器层进行处理的。

    BINARY和VARBINARY存储的事二进制字符串,二进制字符串存储的是字节码而不是字符。MySQL填充BINARY采用的是\0而不是空格,在检索时也不会去掉填充值。MySQL比较BINARY字符串时,每次按一个字节,并且根据该字节的数值进行比较,因此二进制比较比字符比较简单得多,所以更快。

 

  BLOB和TEXT类型

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

    字符类型时:TINYTEXT,SMALLTEXT,TEXT,MEDIUMTEXT,LONGTEXT。

    二进制类型:TINYBLOB,SMALLBLOB,BLOB,MEDIUMBLOB,LONGBLOB。

    MySQL把每个BLOB和TEXT值当作一个独立的对象处理。存储引擎在存储时通常会作特殊处理。当BLOB和TEXT值太大时,InnoDB会使用外部存储区域进行存储,此时每个值在行内需要1~4个字节存储一个指针,然后在外部存储区域存储实际的值。

    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的字段都使用SUBSTRING(column, length)将列的值转换为字符串,这样就可以使用内存临时表了。但要确保街区的子字符串足够短,不会使临时表的大小超过max_heap_table_size或tmp_table_size,超过以后MySQL会将内存临时表转换为MyISAM磁盘临时表。

 

 

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

    枚举列可以把一些不重复的字符串存储成一个预定义的集合。MySQL在存储枚举时非常紧凑,会根据列表值的数量压缩到一个或两个字节中。MySQL在内部会将每个值在列表中的位置保存为整数,并且在表的.frm文件中保存“数字-字符串”映射关系的“查找表”。枚举字段时按照内部存储的整数而不是定义的字符串进行排序的。一种绕过这种限制的方式是按照需要的顺序来定义枚举列。另外也可以在查询中使用FIELD()函数显式地指定排序顺序,但这会导致MySQL无法利用索引消除排序。枚举字符串列表是固定的,添加或删除字符串必须使用ALTER TABLE。因此对于未来可能会改变的字符串,使用枚举不死一个好主意,除非能接受旨在列表末尾添加元素。由于MySQL把每个枚举值保存为整数,并且必须进行查找才能转换为字符串。通常枚举的列表都比较小。

 

 

  日期和时间类型

    MySQL可以使用许多类型来保存日期和时间值。例如YEAR和DATE。MySQL能存储的最小时间粒度为秒。但MySQL可以使用微秒级的粒度进行临时运算。MySQL提供两种日前类型;DATETIME和TIMESTAMP。

    DATETIME精度为秒。它把日期和时间封装到格式为YYYYMMDDHHMMSS的整数中,与时区无关。使用8个字节的存储空间。

    TIMESTAMP只使用了4个字节的存储空间。MySQL提供了FROM_UNIXTIME()函数把Unix时间转换为日期,并提供UNIX_TIMESTAMP()函数把日期转换为Unix的时间戳。TIMESTAMP显示的值也依赖于市区。MySQL服务器,操作系统以及客户端连接都有时区的设置。默认情况下,若插入时没有指定第一个TIMESTAMP列的值,MySQL则设置这个列的值为当前时间。TIMESTAMP列默认为NOT NULL。

    我们应该尽量使用TIMESTAMP。

 

  

  位数据类型

    BIT:BIT是TINYINT的同义词。可以使用BIT列在一列中存储一个或多个true/false值。BIT(1)定义一个包含单个位的字段,BIT列的最大长度是64个位。BIT的行为因存储引擎而异。MyISAM会打包所有的BIT列;Memory和InnoDB位每个BIT列使用一个足够存储的最小整数型来存放,所以不能节省存储空间。MySQL把BIT当作字符串类型,而不是数字类型。当检索BIT(1)的值时,结果是一个包含二进制0或1值的字符串,而不是ASCII的0或1。然而在数字上下文的场景中检索时,结果将是位字符串转换成数字。若想在一个bit的存储空间中存储一个true/false,可以创建一个可以为空的CHAR(0),可以保存NULL或空字符串。

    SET:如果需要保存很多true/false,可以考虑合并这些列到一个SET数据类型,它在MySQL内部是一一些列打包的位的集合来表示的。缺点是改变列的定义的代价比较高:需要ALTER TABLE。一般来说也无法在SET裂伤通过索引来查找。

    在整数列上进行按位操作:一种替代SET的方式是使用一个整数包装一些列的位。好处是可以不实用ALTER TABLE改变字段代表的“枚举值”,缺点是查询语句不好写并且不好理解。

 

 

  选择标识符(identifier)

    一旦选择了一种类型,要确保所有关联表中国年都使用同样的类型。类型之间需要精确匹配,包括像UNSIGNED这样的属性。整数类型通常是标识列最好的选择,因为他们可以很快并可以使用AUTO_INCREMENT。ENUM和SET列适合存储固定信息。应该避免使用字符串类型作为标识列,它们很消耗空间,并且唱比数字类型慢。对于完全随机的的字符串,如MD5(),SHA1(),UUID()等产生的字符串,这些函数生成的新值会任意分布在很大的空间内。因为插入值会随机的写到索引的不同位置,所以INSERT很慢;逻辑上相邻的行分布在磁盘和内存的不同地方,所以SELECT很慢。随机值导致缓存怼所有类型的查询语句效果都很差。若存储UUID值,则应该移除“-”富豪;或者用UNHEX()函数转换UUID值为16字节的数字,并且存储在一个BINAEY(16)列中。检索时可以通过HEX()函数啦格式化为十六进制格式。UUID值虽然分布不均鱼,但是有一定顺序,但还是应该选择递增的整数。

 

  存储IP时应使用无符号整数。MySQL提供INET_ATON()和INET_NTOA()函数在这两种方法之间相互转换。

 

  

  MySQL schema中的陷阱

    太多的列:MySQL的存储引擎API工作时需要在服务器层和存储引擎层之间通过行缓冲格式拷贝数据,然后在服务器层将缓冲内容解码成各个列。从行缓冲功能中国年将编码过的列转换成行数据结构的操作代价是非常高的。MyISAM的定长行结构实际上与服务器层的行结构正好匹配,所以不需要转换。但是MyISAM和变长结构和InnoDB的行结构则总需要转换。转换的代价依赖于列的数量。

    太多的关联:实体——属性——值(EVA)设计模式在MySQL下不能很好的工作。MySQL限制了每个关联操作最多只有61张表。单个查询最好在12个表以内做关联。

    全能的枚举:防止过度使用枚举。

    变相的枚举:枚举列允许在列中存储一组定义值中的单个值,集合(SET)则允许在列中存储一组定义值中的一个或多个值。

    非此发明(Not Invent Here)的NULL:避免使用NULL,可以使用0或某个特殊值,或者空字符串来作为代替。

 

 

  范式和反范式

    1NF(第一范式):强调原子性,即列不能够再被复制

    2NF(第二范式):首先是1NF,且必然有主键,没有包含在主键的列必须完全依赖于主键

    3NF(第三范式):首先是2NF,且非主键列必须直接依赖于主键

 

    在范式话数据库中,每个事实数据会出现并且只出现一次。相反,在反范式化的数据库中,信息是冗余的,可能会存储在多个地方。

    范式的优点

      范式化的更新操作通常比反范式化要快

      当数据较好地范式化时,就只有很少或者没有重复数据,所以只需要修改很少的数据

      范式化的表通常更小,可以更好地放在内存里,所以执行操作会更快

      很少有多余的数据意味着检索列表数据时更少需要DISTINCT或者GROUP BY语句

    范式的缺点是通常需要关联。  

    反范式化的schema因为所有数据都在一张表中,可以很好地避免关联。若不需要关联,则对大部分查询最差的情况是全表扫描。当数据比内存大时可能比关联要快得多,因为避免了随机I/O。

    最常见的反范式化数据的方式是复制或者缓存,在不同的表中存储相同的特定列,可以使用触发器更新缓存值。从父表冗余一些数据倒子表的理由是排序需要。

 

  缓存表和汇总表

    有时挺升性能最好的办法是在同一张表中保存衍生的冗余数据,有时也需要创建一张完全独立的汇总表或缓存表。我们使用缓存表来存储哪些可以比较简单地从schema其他表获取数据的表,汇总表保存的是使用GROUP BY语句聚合数据的表。

    缓存表对优化搜索和检索查询语句很有效。可以对缓存表使用不同的存储引擎。

    当重建汇总表和缓存表时,通常需要保证数据在操作时依然可用。这需要使用影子表。影子表指的是一张在真实表背后创建的表。当完成建表操作后,可以通过一个原子命名操作切换影子表和原表。

    CREATE TABLE t2 AS SELECT * FROM t1; //只有表结构和数据,没有索引,注释,序列等

    CREATE TABLE t2 LIKE t1;//有完整的表结构,但是没有数据

 

  

  物化视图

    物化视图实际上是预先计算并且存储在磁盘上的表,可以通过各种各样的策略刷新和更新。MySQL并不支持物化视图。可以使用Flexviews,它由下面几个部分组成。

      变更数据抓取(change data capture, CDC)功能,可以读取服务器的二进制日志并且解析相关行的变更

      一系列可以帮助创建和管理试图的定义的存储过程

      一些可以应用变更到数据库中国年的物化视图的工具

    相比传统的维护汇总表和缓存表的方法,Flexviews通过提取对源表的更改,可以增量地重新计算物化视图的内容。

 

  

  计数器表

    创建一张独立的表存储技术器可以帮助避免查询缓存实效,并且可以使用本节展示的一些更高级的技巧。例如在高并发下,可以将计数器保存在多行,每次随机选择一行进行更新。

 

 

  加快ALTER TABLE操作的速度

    MySQL执行大部分修改表结构操作的方法是用新的结构创建一个空表,从旧表中查处所有数据插入新表,然后删除旧表。大部分ALTER TABLE会导致MySQL服务中断。可以通过两个技巧:一种是先在一台不提供服务的机器上执行ALTER TABLE操作,然后和提供服务的主库进行切换;另一种技巧是影子拷贝。影子拷贝的技巧是用要求的表结构创建一张和源表无关的新表,然后通过重命名和删表操作交换两张表。

    更改表可以通过ALTER COLUMN修改表中的列,这个命令直接修改.frm文件,因此速度很快。也可以直接修改.frm来改动表达到不新建一张表的目的;修改.frm只适用以下几种方式:

      移除一个列的AUTO_INCREMENT属性

      增加,移除或更改ENUM和SET常量。若移除的是已经有有行数据用到其值的常量,查询将会返回个空字符串。

    基本的技术是为想要的表结构创建一个新的.frm文件,然后用它替换已经存在的那张表的.frm,步骤如下:

      创建一张由相同结构的空表,并进行所需要的更改;(CREATE TABLE t2 LIKE t1;ALTER TABLE t2 MODIFY COLUMN rating ENUM(‘A‘,‘B‘,‘C‘) DEFAULT ‘A‘;)

      执行FLUSH TABLES WITH READ LOCK。它会关闭所有正在使用的表,并且禁止任何表被打开;(FLUSH TABLES WITH READ LOCK;)

      交换.frm文件;(mv t1.frm t1_temp.frm;mv t2.frm f1.frm;mv t1_temp.frm f2.frm)

      执行UNLOCK TABLES来释放第二步的读锁(UNLOCK TABLES)

 

 

  快速创建MyISAM索引

    为了高效地载入数据到MyISAM表中,常用的技巧是先禁用索引,载入数据然后重新启用索引:

    ALTER TABLE test.load_data DISABLED KEYS;

    ALTER TABLE test.load_data ENABLE KEYS;

    这么做的原因是构建索引的工作被延迟到数据完全载入以后,这是可以通过排序来构建索引,使得索引数的碎片更少,更紧凑。缺点是对唯一索引无效。DISABLED KEYS只对非唯一索引有效。

   

  在InnoDB中,有个技巧是先删除所有的非唯一索引,然后增加新的列,最后重新创建删掉的索引。

以上是关于Schema与数据类型优化的主要内容,如果未能解决你的问题,请参考以下文章

MySQL Schema与数据类型优化

《高性能MySQL》——Schema与数据类型优化(笔记)

MySQL之Schema与数据类型优化

Schema与数据类型优化

高性能MySQL—Schema与数据类型优化

第四章:Schema与数据类型优化