《高性能Mysql》- 推荐的DDL设计

整体原则是根据系统将要进行的查询来设计schema。

一、选择合适的数据类型

原则是选择够用的最小数据类型,好处是占用磁盘、内存、CPU缓存空间少,处理时需要的CPU周期也少;优先使用简单类型,如整型比字符操作代价低。

1.1 为列选择合适的数据类型

1)主键列优先选择整数类型,速度快且可使用auto increment;2)用于联表查询的关联列,如film表的filmId和actor表的filmID,无论是否设置外键,推荐使用相同的类型,以避免比较操作时的类型转换;3)尽量指定列为not null,尤其是要建索引的列;如果查询中包含可为null的列,其索引,索引统计,值比较更加复杂难以优化。

1.2 数字类型

1)整数:tinyint、smallint、mediumint、int、bigint分别占用8、16、24、32、64位存储空间,储值范围-2^(N -1) ~2^(N-1)-1,N是位数;可选unsigned属性,如用无符号整数存储IP地址,IP实际是32位无符号整数,用小数点分隔只是方便人们阅读。

2)实数:float、double分别占用32、64位存储空间;使用cpu原生支持的浮点运算进行近似运算;

3)Decimal:cpu不支持decimal计算,mysql自身实现了decimal精确运算;需要额外存储空间和运算开销,只在需要对小数进行精确运算时使用,如财务数据,也可用于比bigint还大的数

1.3 字符类型

选择字符类型时需要考虑字符集charset与字符比较规则collation。

1.3.1 varchar

varchar类型需要额外字节记录实际长度;最大长度不超过255则用1字节,否则2字节,如varchar(10)需要11字节存储,varchar(1000)需要1002字节存储。此外,InnoDB把过长的varchar存储为blob。适用场景包括:1)字符串的最大长度比平均长度大很多;2)很少更新的列;3)使用UTF-8等复杂字符集,每个字符使用多字节存储。

可能引发的内存碎片的问题,当更新操作让行变长并超出页空间时,需要分裂页使得行可以放在页内,产生剩余空间碎片。内存碎片可细分为:1)行碎片,即一行数据被存储在多个地方,InnoDB不允许存在行碎片;2)行间碎片:不同的行可以存储在不同位置,而不是顺序存储;行间碎片影响全表扫描和聚簇扫描的效率,它们在顺序存储的数据中受益;3)剩余空间碎片:数据页有大量剩余空间,导致服务器读取大量不需要的数据。可以通过optimize table、alter table、导出再导入等方式整理数据,去除碎片。

varchar的另一个问题是排序时悲观的按最大长度分配内存。

1.3.2 其他字符类型

1)char:用于短字符串或者定长字符串,如char(1)用于存储Y/N;长度不够时用空格填充;

2)blob/text:采用二进制或字符方式存储;值太大时存储到外部区域,行内存储指针;text有排序规则和字符集,blob没有;只提供前缀排序;各自细分为tiny、small、medium、long。

blob、text引发的性能问题:1)服务器不能在内存临时表中存储blob,如果涉及blob的查询需要临时表,那必然会在磁盘上创建临时表。2)对于blob,innoDB存储一个768字节的前缀在行内,剩余部分如果不超过InnoDB的最大行长8K,则存储行内,否则存储在外部区域。innoDB更新存储在外部的blob字段时,会在外部区域写一个新值到新的位置,不会删除旧值,这样会占用更多空间。外部区域不能通过自适应哈希进行查找。

对应的解决方案:1)如果一张表有很多大字段,最好把他们组合并存到一个列,比如用json/xml;2)考虑对blob列用compress方法压缩后存储

3)binary/varbinary:存储二进制字符串和字节码,字节码的比较操作更快,长度不够时用零字节\0填充;

4)枚举 - 不推荐:在frm文件中预定义枚举类型,保存字符串-数字映射关系;优点是表数据中存储数字占用空间小;缺点是中间转换需要开销,且修改枚举的成本高,需要alter table。

1.3.3 字符集和校对:charset & collation

字符集是二进制到某类字符符号的映射,校对是用于某个字符集的排序规则。每种字符集可以有多种校对规则和一个默认校对规则;但每个校对规则只针对一个字符集。Mysql默认字符集是latin1,对英语和欧洲国家语言有效。字符集、校对规则可通过show characterset和show collation查看。建议数据库采用统一字符集;可以在Mysql服务器、数据库、数据表、列层级指定字符集,且存在层级继承关系。

可配置客户端与服务器之间的连接字符集,客户端服务器可能采用不同的字符集,此时服务器需要负责翻译。服务器假设客户端发送的请求字符集为character set client,收到客户端请求后翻译为字符集character set connection并针对该字符集进行处理,处理完成后服务端返回时采用字符集character set result

比较字符串大小时首先保证两个字符串为相同字符集,可选择采用大小写敏感方式或二进制方式进行字符串比较。

1.4 日期与时间类型

推荐使用以下mysql内置类型存储日期和时间;Mysql存储时间的最小粒度是秒。

1)timestamp - 推荐:4字节,保存1970-1-1 00:00:00Z以来的秒数,只能表示到2038年;显示的时间依赖于时区,时区在mysql服务器、操作系统、客户端连接中配置;默认值为当前时间。

2)datetime:8字节,时间范围1001-9999年;无时区,只是表达时间的文本,来自各时区的请求无区别应答;显示格式yyyy-MM-dd HH:mm:ss    

二、DDL设计的范式与反范式

范式的好处:1)避免冗余数据;2)数据表更小可加载到内存;3)查询中更少的使用使用group by和distinct等开销大的查询;缺点是需要大量联表操作,通常我们需要控制单个查询的联表数目不超过12个表,想起来太阳公司的extract customer大sql。

反范式的好处:1)避免联表;2)更有效的索引策略,如select msgContent from msg join user on userID where user.type=‘vip’ order by msg.published desc limit 10,索引是msg.published。执行计划是扫描msg.published索引,对每条msg数据去user表查看是否vip用户,如果vip用户少则效率低下;如果是一张表,则用(published, usertype)作为索引可以提升查询效率。缺点是:1)数据冗余;2)数据表更大,通常我们需要控制列数不能达到数百列,因为服务层和引擎层之间通过‘行缓冲‘拷贝数据,服务层把行数据解码成各个列,列越多则开销越大。

2.1 范式详解

范式可以理解为数据表结构所满足的某种设计标准的级别;就像家里买建材,最环保的是E0级,其次是E1/E2级等。数据库范式从低到高也分为1NF、2NF、3NF、BCNF、4NF和5NF,且满足高级范式必然满足低级范式,比如符合2NF的数据表必然符合1NF,一般设计到满足3NF就够了,BCNF要求数据表的候选键只能有一个,过于严苛了,候选键是指可以作为主键的属性。

1)1NF第一范式:数据表中每个列不可再分,反例如商品表的列为销售,包含销售数量和售价。

2)2NF:要有主键作为核心属性,其他列是和主键相关的非核心属性。

3)3NF:消除冗余,数据表的列之间不存在函数依赖,比如列A的值确定时,列B的值唯一确定。

3. 特殊场景下的数据表设计

1)缓存表:存储从其他表获取的数据,用于优化搜索;

2)汇总表:存储group by聚合数据;例如网站需要计算前24小时发送的消息数,维护计数器成本高,可以每小时生成汇总表,一个查询就可以做到。可用于计算最活跃用户或者热门话题。

3)计数器表:创建数据表hit_counter用于存储计数器,用于点赞数计数。包含两列slot和cnt,预先添加100行数据,每次更新选择随机槽slot更新,需要获取结果时求和。如果只有一行,互斥锁使得修改计数器的事务只能串行。

4. DDL即表定义的修改

alter table的耗时长,通过下列方式提高alter table期间的数据使用效率:

1)通过影子表保证数据表重建过程中的可用性;例如重建数据表A,则先建立newA数据表并填充数据,填充完成后表A重命名为oldA, newA重命名为表A,支持回滚。这种方式类似于蓝绿部署的思路。2)先在不提供服务的机器上执行alter table,然后和提供服务的机器切换。

你可能感兴趣的:(《高性能Mysql》- 推荐的DDL设计)