一 前言
看到这个标题,不要惊讶哦。或许有部分人会疑问,存入的数据已经固定了,怎么还能优化尼?是的,还是可以优化的。这里引出我最常说的一句话: 数据库优化的本质就是在减少数据量。
接下来的篇幅介绍从哪些方面可以从数据上做调优。
二 结构优化
结构优化涉及的主要是 schema 层面来讲解,围绕表(table)、列(column)、数据类型(data type)、主键(primary key)、外键(foreign key)等方面。
2.1 列(column)
1) Mysql 提供了丰富多样的数据类型供使用者使用。比如数值、字符串(字符)类型提供了多样的可选项。
类型 | 大小 | 范围(有符号) | 范围(无符号) | 用途 |
TINYINT | 1 byte | (-128,127) | (0,255) | 小整数值 |
SMALLINT | 2 bytes | (-32 768,32 767) | (0,65 535) | 大整数值 |
MEDIUMINT | 3 bytes | (-8 388 608,8 388 607) | (0,16 777 215) | 大整数值 |
INT或INTEGER | 4 bytes | (-2 147 483 648,2 147 483 647) | (0,4 294 967 295) | 大整数值 |
BIGINT | 8 bytes | (-9,223,372,036,854,775,808,9 223 372 036 854 775 807) | (0,18 446 744 073 709 551 615) | 极大整数值 |
FLOAT | 4 bytes | (-3.402 823 466 E+38,-1.175 494 351 E-38),0,(1.175 494 351 E-38,3.402 823 466 351 E+38) | 0,(1.175 494 351 E-38,3.402 823 466 E+38) | 单精度 |
DOUBLE | 8 bytes | (-1.797 693 134 862 315 7 E+308,-2.225 073 858 507 201 4 E-308),0,(2.225 073 858 507 201 4 E-308,1.797 693 134 862 315 7 E+308) | 0,(2.225 073 858 507 201 4 E-308,1.797 693 134 862 315 7 E+308) | 双精度浮点数值 |
DECIMAL | 对DECIMAL(M,D) ,如果M>D,为M+2否则为D+2 | 依赖于M和D的值 | 依赖于M和D的值 | 小数值 |
类型 | 大小 | 用途 |
CHAR | 0-255 bytes | 定长字符串 |
VARCHAR | 0-65535 bytes | 变长字符串 |
TINYBLOB | 0-255 bytes | 不超过 255 个字符的二进制字符串 |
TINYTEXT | 0-255 bytes | 短文本字符串 |
BLOB | 0-65 535 bytes | 二进制形式的长文本数据 |
TEXT | 0-65 535 bytes | 长文本数据 |
MEDIUMBLOB | 0-16 777 215 bytes | 二进制形式的中等长度文本数据 |
MEDIUMTEXT | 0-16 777 215 bytes | 中等长度文本数据 |
LONGBLOB | 0-4 294 967 295 bytes | 二进制形式的极大文本数据 |
LONGTEXT | 0-4 294 967 295 bytes | 极大文本数据 |
这么多可选的数据类型就是告诉大家不要遇到数据就用 INT 类型,按业务数据的特点选择较小的整数类型。比如:同一份数据使用 MEDIUMINT 存储就比 INT 少使用25%的空间。
当然也不一定非要选一个最小的类型,你要考虑到业务的变更,给这些列预留一些空间。
2)列尽可能(或者是必须)声明为 NOT NULL。因为 MySQL 对于可为 NULL 的列需要多使用一个字节标识它,且可 NULL 的在索引优化上有着一些弊端。
3)列名尽量简单短小。如有用户表 user, 用户名称尽量使用 name 而非 user_name,本身表已经使表中的列有了一些约束。
更多可以查阅博文《MySQL 规范和优化建议》。
InnoDB 存储引擎支持四种行格式:REDUNDANT,COMPACT, DYNAMIC,和COMPRESSED。默认使用 COMPACT模式。
行格式决定了数据的物理存储方式,比如 COMPACT(紧凑行格式) 相对于 REDUNDANT 减少了约 20% 的行存储空间。 所以可以根据四种格式的特点选择合适的格式。更多请参照文档《InnoDB Row Formats》。
2.3 索引
总所周知在 MySQL 中索引分为两类:聚簇索引和二级索引(辅助索引)。通常情况下大家都会选择使用B+Tree实现其索引结构。
所以聚簇索引实际上在同一个结构中保存了B-Tree索引和数据行。可以理解为你的一张使用了 B+Tree 作为索引结构的表是通过主键来组织的B+Tree,叶子节点保存了一行数据。
二级索引和聚簇索引不同的是它叶子节点存储的是索引字段和主键字段。
所以基于上面的知识点,我们在可以在索引上有这样的优化。
1)主键索引尽量小一些,因为所有二级索引保存的都是主键的值。
2)可根据查询特点创建合适的复合索引,若是查询的列都在复合索引上还能避免回表操作。 3)在较长的列创建索引时可以合理使用最左匹配的特性创建索引,不需要全字段索引。
更多可以查阅博文《MySQL 规范和优化建议》。
这一节主要是将优化的本质,就是需要查询快。当你开始对你的表进行优化了,说明你在查询时候遇到了响应较慢的情况了。这时候上面的结构优化带来的收益对你来说不是很明显(但是不代表不做结构优化)。
这时间我们需要采取曲线救国的线路,用空间换时间。主要围绕几个方面 分库、分表、聚合化、冷热数据分类处理等。
3.1 分库
单机的性能时有极限的,所以当数据多到一定程度我们可以进行合理的分库处理。
举一个比较常见的例子 游戏业务。在该业务下主要的核心模块有用户模块、支付模块。那这种情况往往可以按业务特点把数据库分为用户中心数据库、支付中心数据,分布在不同的机器上。
这样不仅能够减轻单台数据库的压力,也能提高服务的高可用性。
3.2 分表
大家都知道 MySQL 本身也有天花板,一张表的记录不可能无限大。大家可以回想下自己的业务,多半情况下一张表中 99% 的数据不会使用到。
所以这种情况下可以把一张表查分为多张。通常情况下根据数据特点可以分为水平拆分和垂直拆分两种。
水平拆分
1)还是用上面的例子,比如订单表。订单数据是一种有序的数据,所以我们可以根据下单时间进行按月、年等维度进行拆分。
这样就能满足一定范围内的查询且数据量不大。
2)如用户表。相比订单表,用户按理也可以使用注册时间进行拆分。但是用户有个唯一的特性,就是你需要判断用户是否注册。所以不能直接按注册日期进行拆分,这种时候可以采用 一致性哈希算法。
垂直拆分
1)还是用上面的例子,比如用户表。用户有很多属性(地址、电话、生日…),但是这些是不常用的。所以可以把这些属性单独拆出来,用户表只保留一些常用的属性。
3.3 聚合化
这种方式明显的体现了空间换时间的特点。何为聚合化?就是把一组有相同属性的数据进行聚合处理。比如上述的例子中订单数据。
订单数据中有游戏的属性,例如有这样的一个业务:需要看每日每个游戏的总充值,那这种情况就可以提前把订单表的数据按游戏分组聚合成一个游戏每日累充表。
这样就能避免实时查询订单表,还能业务加快查询数据。
3.4 冷热数据分类处理
冷热数据可以根据在近期一段时间内是否会使用到的数据进行划分,大家应该有看到过某些应用不支持实时查看半年之前的订单。
这种情况就是他们做了冷热数据分类处理了,比如半年之前的订单,大多数人是不会查询的。这种时候有点类似上面的分表,可以把这部分数据单独存储。列如存储在机械硬盘、磁带这种价格低廉、容量大、寿命长的介质中。
不好之处就是若是有使用,就是需要一定的时间。但是这种情况相对较少,所以整体是利大于弊。