完爆 90% 的性能毛病,22 点通用绝招介绍(一)

本文涉及的产品
云原生数据库 PolarDB PostgreSQL 版,标准版 2核4GB 50GB
云原生数据库 PolarDB MySQL 版,通用型 2核4GB 50GB
简介: 完爆 90% 的性能毛病,22 点通用绝招介绍(一)

前言

毫不夸张的说咱们后端工程师,无论在哪家公司,呆在哪个团队,做哪个系统,遇到的第一个让人头疼的问题绝对是数据库性能问题。如果我们有一套成熟的方法论,能让大家快速、准确的去选择出合适的优化方案,我相信能够快速准备解决咱么日常遇到的80%甚至90%的性能问题。

从解决问题的角度出发,我们得先了解到问题的原因;其次我们得有一套思考、判断问题的流程方式,让我们合理的站在哪个层面选择方案;最后从众多的方案里面选择一个适合的方案进行解决问题,找到一个合适的方案的前提是我们自己对各种方案之间的优缺点、场景有足够的了解,没有一个方案是完全可以通吃通用的,软件工程没有银弹。

下文的我工作多年以来,曾经使用过的八大方案,结合了平常自己学习收集的一些资料,以系统、全面的方式整理成了这篇博文,也希望能让一些有需要的同行在工作上、成长上提供一定的帮助。

基于 Spring Boot + MyBatis Plus + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能

为什么数据库会慢?

微信图片_20220908095430.png

无论是关系型数据库还是NoSQL,任何存储系统决定于其查询性能的主要有三种:

  • 查找的时间复杂度
  • 数据总量
  • 高负载

而决定于查找时间复杂度主要有两个因素:

  • 查找算法
  • 存储数据结构

无论是哪种存储,数据量越少,自然查询性能就越高,随着数据量增多,资源的消耗(CPU、磁盘读写繁忙)、耗时也会越来越高。

从关系型数据库角度出发,索引结构基本固定是B+Tree,时间复杂度是O(log n),存储结构是行式存储。因此咱们对于关系数据库能优化的一般只有数据量。

而高负载造成原因有高并发请求、复杂查询等,导致CPU、磁盘繁忙等,而服务器资源不足则会导致慢查询等问题。该类型问题一般会选择集群、数据冗余的方式分担压力。

微信图片_20220908095510.png

基于 Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能

应该站在哪个层面思考优化?

微信图片_20220908095535.png

从上图可见,自顶向下的一共有四层,分别是硬件、存储系统、存储结构、具体实现。层与层之间是紧密联系的,每一层的上层是该层的载体;因此越往顶层越能决定性能的上限,同时优化的成本也相对会比较高,性价比也随之越低。以最底层的具体实现为例,那么索引的优化的成本应该是最小的,可以说加了索引后无论是CPU消耗还是响应时间都是立竿见影降低;

然而一个简单的语句,无论如何优化加索引也是有局限的,当在具体实现这层没有任何优化空间的时候就得往上一层【存储结构】思考,思考是否从物理表设计的层面出发优化(如分库分表、压缩数据量等),如果是文档型数据库得思考下文档聚合的结果;如果在存储结构这层优化得没效果,得继续往再上一次进行考虑,是否关系型数据库应该不适合用在现在得业务场景?如果要换存储,那么得换怎样得NoSQL?

所以咱们优化的思路,出于性价比的优先考虑具体实现,实在没有优化空间了再往上一层考虑。当然如果公司有钱,直接使用钞能力,绕过了前面三层,这也是一种便捷的应急处理方式。

该篇文章不讨论顶与底的两个层面的优化,主要从存储结构、存储系统中间两层的角度出发进行探讨。

八大方案总结

微信图片_20220908095649.png

数据库的优化方案核心本质有三种: 减少数据量、用空间换性能、选择合适的存储系统,这也对应了开篇讲解的慢的三个原因:数据总量、高负载、查找的时间复杂度。

这里大概解释下收益类型: 短期收益,处理成本低,能紧急应对,久了则会有技术债务;长期收益则跟短期收益相反,短期内处理成本高,但是效果能长久使用,扩展性会更好。

静态数据意思是,相对改动频率比较低的,也无需过多联表的,where过滤比较少。动态数据与之相反,更新频率高,通过动态条件筛选过滤。

减少数据量

减少数据量类型共有四种方案:数据序列化存储、数据归档、中间表生成、分库分表。

就如上面所说的,无论是哪种存储,数据量越少,自然查询性能就越高,随着数据量增多,资源的消耗(CPU、磁盘读写繁忙)、耗时也会越来越高。

目前市面上的NoSQL基本上都支持分片存储,所以其天然分布式写的能力从数据量上能得到非常的解决方案。而关系型数据库,查找算法与存储结构是可以优化的空间比较少,因此咱们一般思考出发点只有从如何减少数据量的这个角度进行选择优化,因此本类型的优化方案主要针对关系型数据库进行处理。

微信图片_20220908095714.png

数据归档

微信图片_20220908095736.png

注意点:别一次性迁移数量过多,建议低频率多次限量迁移。像MySQL由于删除数据后是不会释放空间的,可以执行命令OPTIMIZE TABLE释放存储空间,但是会锁表,如果存储空间还满足,可以不执行。

建议优先考虑该方案,主要通过数据库作业把非热点数据迁移到历史表,如果需要查历史数据,可新增业务入口路由到对应的历史表(库)。

微信图片_20220908095807.png

中间表(结果表)

微信图片_20220908095828.png

中间表(结果表)其实就是利用调度任务把复杂查询的结果跑出来存储到一张额外的物理表,因为这张物理表存放的是通过跑批汇总后的数据,因此可以理解成根据原有的业务进行了高度的数据压缩。

以报表为例,如果一个月的源数据有数十万,我们通过调度任务以月的维度生成,那么等于把原有的数据压缩了几十万分之一;接下来的季报和年报可以根据月报*N来进行统计,以这种方式处理的数据,就算三年、五年甚至十年数据量都可以在接受范围之内,而且可以精确计算得到。

那么数据的压缩比率是否越低越好?下面有一段口诀:

  • 字段越多,粒度越细,灵活性越高,可以以中间表进行不同业务联表处理。
  • 字段越少,粒度越粗,灵活性越低,一般作为结果表查询出来。

数据序列化存储

微信图片_20220908095901.png

微信图片_20220908095904.png

在数据库以序列化存储的方式,对于一些不需要结构化存储的业务来说是一种很好减少数据量的方式,特别是对于一些M*N的数据量的业务场景,如果以M作为主表优化,那么就可以把数据量维持最多是M的量级。另外像订单的地址信息,这种业务一般是不需要根据里面的字段检索出来,也比较适合。

这种方案我认为属于一种临时性的优化方案,无论是从序列化后丢失了部份字段的查询能力,还是这方案的可优化性都是有限的。

分库分表

分库分表作为数据库优化的一种非常经典的优化方案,特别是在以前NoSQL还不是很成熟的年代,这个方案就如救命草一般的存在。

如今也有不少同行也会选择这种优化方式,但是从我角度来看,分库分表是一种优化成本很大的方案。这里我有几个建议:

  • 分库分表是实在没有办法的办法,应放到最后选择。
  • 优先选择NoSQL代替,因为NoSQL诞生基本上为了扩展性与高性能。
  • 究竟分库还是分表?量大则分表,并发高则分库
  • 不考虑扩容,一部做到位。因为技术更新太快了,每3-5年一大变。

拆分方式

微信图片_20220908095936.png

只要涉及到这个拆,那么无论是微服务也好,分库分表也好,拆分的方式主要分两种:垂直拆分、水平拆分

垂直拆分更多是从业务角度进行拆分 ,主要是为了降低业务耦合度;此外以SQL Server为例,一页是8KB存储,如果在一张表里字段越多,一行数据自然占的空间就越大,那么一页数据所存储的行数就自然越少,那么每次查询所需要IO则越高因此性能自然也越慢;因此反之,减少字段也能很好提高性能。之前我听说某些同行的表有80个字段,几百万的数据就开始慢了。

水平拆分更多是从技术角度进行拆分 ,拆分后每张表的结构是一模一样的,简而言之就是把原有一张表的数据,通过技术手段进行分片到多张表存储,从根本上解决了数据量的问题。

微信图片_20220908100021.png

路由方式

微信图片_20220908100045.png

进行水平拆分后,根据分区键(sharding key)原来应该在同一张表的数据拆解写到不同的物理表里,那么查询也得根据分区键进行定位到对应的物理表从而把数据给查询出来。

路由方式一般有三种区间范围、Hash、分片映射表,每种路由方式都有自己的优点和缺点,可以根据对应的业务场景进行选择。

区间范围根据某个元素的区间的进行拆分,以时间为例子,假如有个业务我们希望以月为单位拆分那么表就会拆分像 table_2022-04,这种对于文档型、ElasticSearch这类型的NoSQL也适用,无论是定位查询,还是日后清理维护都是非常的方便的。那么缺点也明显,会因为业务独特性导致数据不平均,甚至不同区间范围之间的数据量差异很大。

Hash也是一种常用的路由方式,根据Hash算法取模以数据量均匀分别存储在物理表里,缺点是对于带分区键的查询依赖特别强,如果不带分区键就无法定位到具体的物理表导致相关所有表都查询一次,而且在分库的情况下对于Join、聚合计算、分页等一些RDBMS的特性功能还无法使用。

微信图片_20220908100119.png

一般分区键就一个,假如有时候业务场景得用不是分区键的字段进行查询,那么难道就必须得全部扫描一遍?其实可以使用分片映射表的方式,简单来说就是额外有一张表记录额外字段与分区键的映射关系。

举个例子,有张订单表,原本是以UserID作为分区键拆分的,现在希望用OrderID进行查询,那么得有额外得一张物理表记录了OrderID与UserID的映射关系。因此得先查询一次映射表拿到分区键,再根据分区键的值路由到对应的物理表查询出来。

可能有些朋友会问,那这映射表是否多一个映射关系就多一张表,还是多个映射关系在同一张表。我优先建议单独处理,如果说映射表字段过多,那跟不进行水平拆分时的状态其实就是一致的,这又跑回去的老问题。



相关实践学习
使用PolarDB和ECS搭建门户网站
本场景主要介绍基于PolarDB和ECS实现搭建门户网站。
阿里云数据库产品家族及特性
阿里云智能数据库产品团队一直致力于不断健全产品体系,提升产品性能,打磨产品功能,从而帮助客户实现更加极致的弹性能力、具备更强的扩展能力、并利用云设施进一步降低企业成本。以云原生+分布式为核心技术抓手,打造以自研的在线事务型(OLTP)数据库Polar DB和在线分析型(OLAP)数据库Analytic DB为代表的新一代企业级云原生数据库产品体系, 结合NoSQL数据库、数据库生态工具、云原生智能化数据库管控平台,为阿里巴巴经济体以及各个行业的企业客户和开发者提供从公共云到混合云再到私有云的完整解决方案,提供基于云基础设施进行数据从处理、到存储、再到计算与分析的一体化解决方案。本节课带你了解阿里云数据库产品家族及特性。
相关文章
|
4月前
|
JSON C# 开发者
💡探索C#语言进化论:揭秘.NET开发效率飙升的秘密武器💼
【8月更文挑战第28天】C#语言凭借其强大的功能与易用性深受开发者喜爱。伴随.NET平台演进,C#持续引入新特性,如C# 7.0的模式匹配,让处理复杂数据结构更直观简洁;C# 8.0的异步流则使异步编程更灵活高效,无需一次性加载全部数据至内存。通过示例展示了模式匹配简化JSON解析及异步流实现文件逐行读取的应用。此外,C# 8.0还提供了默认接口成员和可空引用类型等特性,进一步提高.NET开发效率与代码可维护性。随着C#的发展,未来的.NET开发将更加高效便捷。
64 1
|
4月前
|
Java 缓存 数据库连接
揭秘!Struts 2性能翻倍的秘诀:不可思议的优化技巧大公开
【8月更文挑战第31天】《Struts 2性能优化技巧》介绍了提升Struts 2 Web应用响应速度的关键策略,包括减少配置开销、优化Action处理、合理使用拦截器、精简标签库使用、改进数据访问方式、利用缓存机制以及浏览器与网络层面的优化。通过实施这些技巧,如懒加载配置、异步请求处理、高效数据库连接管理和启用GZIP压缩等,可显著提高应用性能,为用户提供更快的体验。性能优化需根据实际场景持续调整。
81 0
|
4月前
|
开发者 前端开发 Apache
【绝不错过!】揭秘Wicket大神级插件,带你飞越编程极限,探索Web应用开发新大陆!
【8月更文挑战第31天】Apache Wicket是一个成熟的Java Web框架,以其组件化体系结构、直观的API及对MVC的支持著称。其活跃社区贡献了大量插件和扩展,显著提升了Wicket的功能性。本文推荐几个实用插件,如**Wicket Ajax Support**,可轻松添加Ajax功能,提升用户体验;**Bootstrap for Wicket**则将Bootstrap与Wicket结合,美化应用界面。
44 0
|
5月前
|
测试技术
ACL 2024:大模型性能掺水严重?北大交出答卷:交互评估+动态出题,死记硬背也没用
【7月更文挑战第8天】北大研究团队推出KIEval框架,针对大语言模型(LLMs)的性能评估进行创新。KIEval采用互动评估和动态出题,通过多轮基于知识的对话测试模型理解和应用能力,旨在减少数据污染影响,挑战死记硬背的评估。然而,该方法可能增加计算需求,且评估结果可能受主观因素影响,不适用于所有类型LLMs。[论文链接:](https://arxiv.org/abs/2402.15043)**
97 24
|
4月前
|
算法 C++
惊爆!KPM算法背后的秘密武器:一行代码揭秘字符串最小周期的终极奥义,让你秒变编程界周期大师!
【8月更文挑战第4天】字符串最小周期问题旨在找出字符串中最短重复子串的长度。KPM(实为KMP,Knuth-Morris-Pratt)算法,虽主要用于字符串匹配,但其生成的前缀函数(next数组)也可用于求解最小周期。核心思想是构建LPS数组,记录模式串中每个位置的最长相等前后缀长度。对于长度为n的字符串S,其最小周期T可通过公式ans = n - LPS[n-1]求得。通过分析周期字符串的特性,可证明该方法的有效性。提供的C++示例代码展示了如何计算给定字符串的最小周期,体现了KPM算法在解决此类问题上的高效性。
87 0
|
7月前
|
编解码 安全 定位技术
典型崩溃问题集锦
典型崩溃问题集锦
52 0
|
消息中间件 缓存 Java
牛掰!阿里人用7部分讲明白百亿级高并发系统(全彩版小册开源)
高并发 提到“高并发”相信你们应该都不会感到陌生!此时你脑中应该会浮现好多有关高并发的:业务急剧增长、电商购物、电商秒杀、12306抢票、淘宝天猫各种活动等;都是需要用到高并发的,那么如何去设计一个高并发系统抵挡这些冲击呢? 其实这也是一道很常见的面试题,但是大多数应聘者都不知如何回答,从何答起。对于一个Java程序员来讲,,更关注的是不是系统架构层面的呢?从原本的定时秒杀,到现在各种活动的预热、拼团、定金膨胀、百亿补贴、跨店满减以及更复杂的组合优惠,让用户摸不到头脑,虽然这些都扰乱了用户购买的节奏,但是也一直保持着持续升温的状态。
推荐5款你可能没见过的效率软件
你有没有想过,有些软件能让你的电脑用起来更方便,更快,更好看?这篇文章就为你介绍了五款这样的软件,它们分别是BreeZip,ClipClip,燃精灵,Sticky Notes和Tabby。下面我们来看看它们都能做什么吧。
106 1
|
算法 Python
以为是高性能神仙算法,一看源代码才发现...
以为是高性能神仙算法,一看源代码才发现...
115 0
|
存储 SQL 缓存
如何设计一个支持一亿用户的系统,心中有方案遇事不慌!
如何设计一个支持一亿用户的系统,心中有方案遇事不慌!
221 0
如何设计一个支持一亿用户的系统,心中有方案遇事不慌!