前言
毫不夸张的说咱们后端工程师,无论在哪家公司,呆在哪个团队,做哪个系统,遇到的第一个让人头疼的问题绝对是数据库性能问题。如果我们有一套成熟的方法论,能让大家快速、准确的去选择出合适的优化方案,我相信能够快速准备解决咱么日常遇到的80%甚至90%的性能问题。
从解决问题的角度出发,我们得先了解到问题的原因;其次我们得有一套思考、判断问题的流程方式,让我们合理的站在哪个层面选择方案;最后从众多的方案里面选择一个适合的方案进行解决问题,找到一个合适的方案的前提是我们自己对各种方案之间的优缺点、场景有足够的了解,没有一个方案是完全可以通吃通用的,软件工程没有银弹。
下文的我工作多年以来,曾经使用过的八大方案,结合了平常自己学习收集的一些资料,以系统、全面的方式整理成了这篇博文,也希望能让一些有需要的同行在工作上、成长上提供一定的帮助。
为什么数据库会慢?
慢的本质 |
|
查找的时间复杂度 |
查找算法 |
存储数据结构 |
|
数据总量 |
数据拆分 |
高负载 |
CPU、磁盘繁忙 |
无论是关系型数据库还是NoSQL,任何存储系统决定于其查询性能的主要有三种:
- 查找的时间复杂度
- 数据总量
- 高负载
而决定于查找时间复杂度主要有两个因素:
- 查找算法
- 存储数据结构
无论是哪种存储,数据量越少,自然查询性能就越高,随着数据量增多,资源的消耗(CPU、磁盘读写繁忙)、耗时也会越来越高。
从关系型数据库角度出发,索引结构基本固定是B+Tree,时间复杂度是O(log n),存储结构是行式存储。因此咱们对于关系数据库能优化的一般只有数据量。
而高负载造成原因有高并发请求、复杂查询等,导致CPU、磁盘繁忙等,而服务器资源不足则会导致慢查询等问题。该类型问题一般会选择集群、数据冗余的方式分担压力
。
应该站在哪个层面思考优化?
从上图可见,自顶向下的一共有四层,分别是硬件、存储系统、存储结构、具体实现。层与层之间是紧密联系的,每一层的上层是该层的载体;因此越往顶层越能决定性能的上限,同时优化的成本也相对会比较高,性价比也随之越低。以最底层的具体实现为例,那么索引的优化的成本应该是最小的,可以说加了索引后无论是CPU消耗还是响应时间都是立竿见影降低;然而一个简单的语句,无论如何优化加索引也是有局限的,当在具体实现这层没有任何优化空间的时候就得往上一层【存储结构】思考,思考是否从物理表设计的层面出发优化(如分库分表、压缩数据量等),如果是文档型数据库得思考下文档聚合的结果;如果在存储结构这层优化得没效果,得继续往再上一次进行考虑,是否关系型数据库应该不适合用在现在得业务场景?如果要换存储,那么得换怎样得NoSQL?
所以咱们优化的思路,出于性价比的优先考虑具体实现,实在没有优化空间了再往上一层考虑。当然如果公司有钱,直接使用钞能力,绕过了前面三层,这也是一种便捷的应急处理方式。
该篇文章不讨论顶与底的两个层面的优化,主要从存储结构、存储系统中间两层的角度出发进行探讨。
八大方案总结
方案总览 |
||||
方案类型 |
方案描述 |
数据类型 |
收益类型 |
应对场景 |
减少数据量 |
数据序列化存储 |
静态数据 |
短期收益 |
大数据量 |
数据归档 |
动态数据 |
中期收益 |
大数据量 |
|
中间表生成 |
静态数据 |
长期收益 |
大数据量、高负载 |
|
分库分表 |
动态数据 |
长期收益 |
大数据量、高负载 |
|
用空间换性能 |
分布式缓存 |
静态数据 |
短期收益 |
高负载 |
一主多从 |
动态数据 |
中期收益 |
高负载 |
|
选择合适的存储系统 |
CQRS |
动态数据 |
长期收益 |
大数据量、高负载 |
更换存储系统 |
动态数据 |
长期收益 |
大数据量、高负载 |
数据库的优化方案核心本质有三种:减少数据量、用空间换性能、选择合适的存储系统,这也对应了开篇讲解的慢的三个原因:数据总量、高负载、查找的时间复杂度。
这里大概解释下收益类型:短期收益,处理成本低,能紧急应对,久了则会有技术债务;长期收益则跟短期收益相反,短期内处理成本高,但是效果能长久使用,扩展性会更好。
静态数据意思是,相对改动频率比较低的,也无需过多联表的,where过滤比较少。动态数据与之相反,更新频率高,通过动态条件筛选过滤。
减少数据量
减少数据量类型共有四种方案:数据序列化存储、数据归档、中间表生成、分库分表。
就如上面所说的,无论是哪种存储,数据量越少,自然查询性能就越高,随着数据量增多,资源的消耗(CPU、磁盘读写繁忙)、耗时也会越来越高。目前市面上的NoSQL基本上都支持分片存储,所以其天然分布式写的能力从数据量上能得到非常的解决方案。而关系型数据库,查找算法与存储结构是可以优化的空间比较少,因此咱们一般思考出发点只有从如何减少数据量的这个角度进行选择优化,因此本类型的优化方案主要针对关系型数据库进行处理。
数据归档 |
|||
做法 |
场景 |
优点 |
缺点 |
利用数据库作业,定时把历史数据移到历史表或者库 |
局部的热点数据 |
结构无需改动,少侵入性 |
热点数据过多仍会导致性能问题 |
注意点:别一次性迁移数量过多,建议低频率多次限量迁移。像MySQL由于删除数据后是不会释放空间的,可以执行命令OPTIMIZE TABLE释放存储空间,但是会锁表,如果存储空间还满足,可以不执行。
建议优先考虑该方案,主要通过数据库作业把非热点数据迁移到历史表,如果需要查历史数据,可新增业务入口路由到对应的历史表(库)。
中间表(结果表)
中间表(结果表) |
|||
做法 |
场景 |
优点 |
缺点 |
通过调度任务定时,把某个业务以多个维度进行聚合分组 |
报表型、排行榜等静态数据 |
压缩比率大 |
需要开发人员针对场景业务进行开发 |
中间表(结果表)其实就是利用调度任务把复杂查询的结果跑出来存储到一张额外的物理表,因为这张物理表存放的是通过跑批汇总后的数据,因此可以理解成根据原有的业务进行了高度的数据压缩。以报表为例,如果一个月的源数据有数十万,我们通过调度任务以月的维度生成,那么等于把原有的数据压缩了几十万分之一;接下来的季报和年报可以根据月报*N来进行统计,以这种方式处理的数据,就算三年、五年甚至十年数据量都可以在接受范围之内,而且可以精确计算得到。
那么数据的压缩比率是否越低越好?下面有一段口诀:
- 字段越多,粒度越细,灵活性越高,可以以中间表进行不同业务联表处理。
- 字段越少,粒度越粗,灵活性越低,一般作为结果表查询出来。
数据序列化存储
数据序列化存储 |
|||
做法 |
场景 |
优点 |
缺点 |
把一对多的数据,通过序列化字符串存储 |
不需要要求所有字段作为结构化存储 |
压缩比率高 |
序列化的字段无法联表 |
在数据库以序列化存储的方式,对于一些不需要结构化存储的业务来说是一种很好减少数据量的方式,特别是对于一些M*N的数据量的业务场景,如果以M作为主表优化,那么就可以把数据量维持最多是M的量级。另外像订单的地址信息,这种业务一般是不需要根据里面的字段检索出来,也比较适合。
这种方案我认为属于一种临时性的优化方案,无论是从序列化后丢失了部份字段的查询能力,还是这方案的可优化性都是有限的。