车陂手机网站建设电话,3d建模做一单多少钱,网站建设的实训心得,wordpress访客ip记录毫不夸张的说咱们后端工程师#xff0c;无论在哪家公司#xff0c;呆在哪个团队#xff0c;做哪个系统#xff0c;遇到的第一个让人头疼的问题绝对是数据库性能问题。如果我们有一套成熟的方法论#xff0c;能让大家快速、准确的去选择出合适的优化方案#xff0c;我相信…毫不夸张的说咱们后端工程师无论在哪家公司呆在哪个团队做哪个系统遇到的第一个让人头疼的问题绝对是数据库性能问题。如果我们有一套成熟的方法论能让大家快速、准确的去选择出合适的优化方案我相信能够快速准备解决咱么日常遇到的80%甚至90%的性能问题。 从解决问题的角度出发我们得先了解到问题的原因其次我们得有一套思考、判断问题的流程方式让我们合理的站在哪个层面选择方案最后从众多的方案里面选择一个适合的方案进行解决问题找到一个合适的方案的前提是我们自己对各种方案之间的优缺点、场景有足够的了解没有一个方案是完全可以通吃通用的软件工程没有银弹。 下文是我工作多年以来曾经使用过的八大方案结合了平常自己学习收集的一些资料以系统、全面的方式整理成了这篇博文也希望能让一些有需要的同行在工作上、成长上提供一定的帮助。
为什么数据库会慢 无论是关系型数据库还是 NoSQL任何存储系统决定于其查询性能的主要有三种
查找的时间复杂度数据总量高负载
而决定于查找时间复杂度主要有两个因素
查找算法存储数据结构
无论是哪种存储数据量越少自然查询性能就越高随着数据量增多资源的消耗CPU、磁盘读写繁忙、耗时也会越来越高。从关系型数据库角度出发索引结构基本固定是BTree时间复杂度是O(log n)存储结构是行式存储。因此咱们对于关系数据库能优化的一般只有数据量。 而高负载造成原因有高并发请求、复杂查询等导致CPU、磁盘繁忙等而服务器资源不足则会导致慢查询等问题。该类型问题一般会选择集群、数据冗余的方式分担压力。 应该站在哪个层面思考优化 从上图可见自顶向下的一共有四层分别是硬件、存储系统、存储结构、具体实现。 层与层之间是紧密联系的每一层的上层是该层的载体因此越往顶层越能决定性能的上限同时优化的成本也相对会比较高性价比也随之越低。 以最底层的具体实现为例那么索引的优化的成本应该是最小的可以说加了索引后无论是 CPU 消耗还是响应时间都是立竿见影降低。 然而一个简单的语句无论如何优化加索引也是有局限的当在具体实现这层没有任何优化空间的时候就得往上一层【存储结构】思考思考是否从物理表设计的层面出发优化如分库分表、压缩数据量等如果是文档型数据库得思考下文档聚合的结果。 如果在存储结构这层优化得没效果得继续往再上一次进行考虑是否关系型数据库应该不适合用在现在得业务场景 如果要换存储那么得换什么样的 NoSQL?所以咱们优化的思路出于性价比的优先考虑具体实现实在没有优化空间了再往上一层考虑。 当然如果公司有钱直接使用钞能力绕过了前面三层这也是一种便捷的应急处理方式。 该篇文章不讨论顶与底的两个层面的优化主要从存储结构、存储系统中间两层的角度出发进行探讨。
八大方案总结 数据库的优化方案核心本质有三种减少数据量、用空间换性能、选择合适的存储系统。 这也对应了开篇讲解的慢的三个原因数据总量、高负载、查找的时间复杂度。 这里大概解释下收益类型短期收益处理成本低能紧急应对久了则会有技术债务长期收益则跟短期收益相反短期内处理成本高但是效果能长久使用扩展性会更好。 静态数据意思是相对改动频率比较低的也无需过多联表的where 过滤比较少。动态数据与之相反更新频率高通过动态条件筛选过滤。
减少数据量
减少数据量类型共有四种方案数据序列化存储、数据归档、中间表生成、分库分表。 就如上面所说的无论是哪种存储数据量越少自然查询性能就越高随着数据量增多资源的消耗CPU、磁盘读写繁忙、耗时也会越来越高。 目前市面上的 NoSQL 基本上都支持分片存储所以其天然分布式写的能力从数据量上能得到非常的解决方案。而关系型数据库查找算法与存储结构是可以优化的空间比较少因此咱们一般思考出发点只有从如何减少数据量的这个角度进行选择优化因此本类型的优化方案主要针对关系型数据库进行处理。 数据归档 注意点别一次性迁移数量过多建议低频率多次限量迁移。 像MySQL由于删除数据后是不会释放空间的可以执行命令OPTIMIZE TABLE释放存储空间但是会锁表如果存储空间还满足可以不执行。 建议优先考虑该方案主要通过数据库作业把非热点数据迁移到历史表如果需要查历史数据可新增业务入口路由到对应的历史表库。 在数据库以序列化存储的方式对于一些不需要结构化存储的业务来说是一种很好减少数据量的方式特别是对于一些M*N的数据量的业务场景如果以M作为主表优化那么就可以把数据量维持最多是M的量级。 另外像订单的地址信息这种业务一般是不需要根据里面的字段检索出来也比较适合。这种方案我认为属于一种临时性的优化方案无论是从序列化后丢失了部份字段的查询能力还是这方案的可优化性都是有限的。
中间表结果表 中间表结果表其实就是利用调度任务把复杂查询的结果跑出来存储到一张额外的物理表因为这张物理表存放的是通过跑批汇总后的数据因此可以理解成根据原有的业务进行了高度的数据压缩。 以报表为例如果一个月的源数据有数十万我们通过调度任务以月的维度生成那么等于把原有的数据压缩了几十万分之一。接下来的季报和年报可以根据月报 *N 来进行统计以这种方式处理的数据就算三年、五年甚至十年数据量都可以在接受范围之内而且可以精确计算得到。 那么数据的压缩比率是否越低越好下面有一段口诀
字段越多粒度越细灵活性越高可以以中间表进行不同业务联表处理。字段越少粒度越粗灵活性越低一般作为结果表查询出来。
数据序列化存储 分库分表
分库分表作为数据库优化的一种非常经典的优化方案特别是在以前 NoSQL 还不是很成熟的年代这个方案就如救命草一般的存在。 如今也有不少同行也会选择这种优化方式但是从我角度来看分库分表是一种优化成本很大的方案。这里我有几个建议
分库分表是实在没有办法的办法应放到最后选择。优先选择 NoSQL 代替因为 NoSQL 诞生基本上为了扩展性与高性能。究竟分库还是分表量大则分表并发高则分库不考虑扩容一部做到位。因为技术更新太快了每 3-5 年一大变。
拆分方式 只要涉及到这个拆那么无论是微服务也好分库分表也好拆分的方式主要分两种垂直拆分、水平拆分。 垂直拆分更多是从业务角度进行拆分主要是为了降低业务耦合度此外以 SQL Server 为例一页是 8KB 存储如果在一张表里字段越多一行数据自然占的空间就越大那么一页数据所存储的行数就自然越少那么每次查询所需要 IO 则越高因此性能自然也越慢因此反之减少字段也能很好提高性能。 之前我听说某些同行的表有 80 个字段几百万的数据就开始慢了。 水平拆分更多是从技术角度进行拆分拆分后每张表的结构是一模一样的简而言之就是把原有一张表的数据通过技术手段进行分片到多张表存储从根本上解决了数据量的问题。 路由方式 进行水平拆分后根据分区键sharding key原来应该在同一张表的数据拆解写到不同的物理表里那么查询也得根据分区键进行定位到对应的物理表从而把数据给查询出来。路由方式一般有三种区间范围、Hash、分片映射表。 每种路由方式都有自己的优点和缺点可以根据对应的业务场景进行选择。区间范围根据某个元素的区间的进行拆分以时间为例子假如有个业务我们希望以月为单位拆分那么表就会拆分像 table_2022-04这种对于文档型、ElasticSearch 这类型的 NoSQL 也适用无论是定位查询还是日后清理维护都是非常的方便的。 那么缺点也明显会因为业务独特性导致数据不平均甚至不同区间范围之间的数据量差异很大。Hash 也是一种常用的路由方式根据 Hash 算法取模以数据量均匀分别存储在物理表里缺点是对于带分区键的查询依赖特别强如果不带分区键就无法定位到具体的物理表导致相关所有表都查询一次而且在分库的情况下对于 Join、聚合计算、分页等一些 RDBMS 的特性功能还无法使用。 一般分区键就一个假如有时候业务场景得用不是分区键的字段进行查询那么难道就必须得全部扫描一遍其实可以使用分片映射表的方式。简单来说就是额外有一张表记录额外字段与分区键的映射关系。 举个例子有张订单表原本是以 UserID 作为分区键拆分的现在希望用 OrderID 进行查询那么得有额外得一张物理表记录了 OrderID 与 UserID 的映射关系。因此得先查询一次映射表拿到分区键再根据分区键的值路由到对应的物理表查询出来。 可能有些朋友会问那这映射表是否多一个映射关系就多一张表还是多个映射关系在同一张表我优先建议单独处理如果说映射表字段过多那跟不进行水平拆分时的状态其实就是一致的这又跑回去的老问题。
用空间换性能
该类型的两个方案都是用来应对高负载的场景方案有以下两种分布式缓存、一主多从。 与其说这个方案叫用空间换性能我认为用空间换资源更加贴切一些。因此两个方案的本质主要通数据冗余、集群等方式分担负载压力。 对于关系型数据库而言因为他的ACID特性让它天生不支持写的分布式存储但是它依然天然的支持分布式读。 分布式缓存 缓存层级可以分好几种客户端缓存、API服务本地缓存和分布式缓存。 咱们这次只聊分布式缓存。一般我们选择分布式缓存系统都会优先选择 NoSQL 的键值型数据库例如 Memcached、Redis如今 Redis 的数据结构多样性高性能易扩展性也逐渐占据了分布式缓存的主导地位。 缓存策略也主要有很多种Cache-Aside、Read/Wirte-Through、Write-Back咱们用得比较多的方式主要 Cache-Aside具体流程可看下图 我相信大家对分布式缓存相对都比较熟悉了但是我在这里还是有几个注意点希望提醒一下大家
避免滥用缓存
缓存应该是按需使用从 28 法则来看80% 的性能问题由主要的 20% 的功能引起。 滥用缓存的后果会导致维护成本增大而且有一些数据一致性的问题也不好定位。特别像一些动态条件的查询或者分页key 的组装是多样化的量大又不好用 keys 指令去处理当然我们可以用额外的一个 key 把记录数据的 key 以集合方式存储删除时候做两次查询先查 Key 的集合然后再遍历 Key 集合把对应的内容删除。 这一顿操作下来无疑是非常废功夫的谁弄谁知道。 避免缓存击穿
当缓存没有数据就得跑去数据库查询出来这就是缓存穿透。 假如某个时间临界点数据是空的例如周排行榜穿透过去的无论查找多少次数据库仍然是空而且该查询消耗 CPU 相对比较高并发一进来因为缺少了缓存层的对高并发的应对这个时候就会因为并发导致数据库资源消耗过高这就是缓存击穿。 数据库资源消耗过高就会导致其他查询超时等问题。该问题的解决方案也简单对于查询到数据库的空结果也缓存起来但是给一个相对快过期的时间。 有些同行可能又会问这样不就会造成了数据不一致了么一般有数据同步的方案像分布式缓存、后续会说的一主多从、CQRS只要存在数据同步这几个字那就意味着会存在数据一致性的问题因此如果使用上述方案对应的业务场景应允许容忍一定的数据不一致。
不是所有慢查询都适用
一般来说慢的查询都意味着比较吃资源的CPU、磁盘I/O。 举个例子假如某个查询功能需要 3 秒时间串行查询的时候并没什么问题我们继续假设这功能每秒大概 QPS 为 100那么在第一次查询结果返回之前接下来的所有查询都应该穿透到数据库也就意味着这几秒时间有 300 个请求到数据库如果这个时候数据库 CPU 达到了 100%那么接下来的所有查询都会超时也就是无法有第一个查询结果缓存起来从而还是形成了缓存击穿。
一主多从 常用的分担数据库压力还有一种常用做法就是读写分离、一主多从。 咱们都是知道关系型数据库天生是不具备分布式分片存储的也就是不支持分布式写但是它天然的支持分布式读。一主多从是部署多台从库只读实例通过冗余主库的数据来分担读请求的压力路由算法可有代码实现或者中间件解决具体可以根据团队的运维能力与代码组件支持视情况选择。 一主多从在还没找到根治方案前是一个非常好的应急解决方案特别是在现在云服务的年代扩展从库是一件非常方便的事情而且一般情况只需要运维或者 DBA 解决就行无需开发人员接入。 当然这方案也有缺点因为数据无法分片所以主从的数据量完全冗余过去也会导致高的硬件成本。从库也有其上限从库过多了会主库的多线程同步数据的压力。 选择合适的存储系统 NoSQL 主要以下五种类型键值型、文档型、列型、图型、搜素引擎。 不同的存储系统直接决定了查找算法、存储数据结构也应对了需要解决的不同的业务场景。NoSQL的出现也解决了关系型数据库之前面临的难题性能、高并发、扩展性等。 例如ElasticSearch 的查找算法是倒排索引可以用来代替关系型数据库的低性能、高消耗的 Like 搜索全表扫描。而 Redis 的 Hash 结构决定了时间复杂度为 O(1)还有它的内存存储结合分片集群存储方式以至于可以支撑数十万 QPS。 因此本类型的方案主要有两种CQRS、替换选择存储这两种方案的最终本质基本是一样的主要使用合适存储来弥补关系型数据库的缺点只不过切换过渡的方式会有点不一样。 CQRS
CQS命令查询分离指同一个对象中作为查询或者命令的方法每个方法或者返回的状态要么改变状态但不能两者兼备 讲解 CQRS 前得了解 CQRS有些小伙伴看了估计还没不是很清晰我这里用通俗的话解释某个对象的数据访问的方法里要么只是查询要么只是写入更新。 而 CQRS命令查询职责分离基于 CQS 的基础上用物理数据库来写入更新而用另外的存储系统来查询数据。 因此我们在某些业务场景进行存储架构设计时可以通过关系型数据库的 ACID 特性进行数据的更新与写入用 NoSQL 的高性能与扩展性进行数据的查询处理这样的好处就是关系型数据库和 NoSQL 的优点都可以兼得同时对于某些业务不适于一刀切的替换存储的也可以有一个平滑的过渡。 从代码实现角度来看不同的存储系统只是调用对应的接口 API因此 CQRS 的难点主要在于如何进行数据同步。
数据同步方式 一般讨论到数据同步的方式主要是分推和拉推指的是由数据变更端通过直接或者间接的方式把数据变更的记录发送到接收端从而进行数据的一致性处理这种主动的方式优点是实时性高。 拉指的是接收端定时的轮询数据库检查是否有数据需要进行同步这种被动的方式从实现角度来看比推简单因为推是需要数据变更端支持变更日志的推送的。 而推的方式又分两种CDC变更数据捕获和领域事件。对于一些旧的项目来说某些业务的数据入口非常多无法完整清晰的梳理清楚这个时候 CDC 就是一种非常好的方式只要从最底层数据库层面把变更记录取到就可。 对于已经服务化的项目来说领域事件是一种比较舒服的方式因为 CDC 是需要数据库额外开启功能或者部署额外的中间件而领域事件则不需要从代码可读性来看会更高也比较开发人员的维护思维模式。 替换选择存储系统 因为从本质来看该模式与 CQRS 的核心本质是一样的主要是要对 NoSQL 的优缺点有一个全面认识这样才能在对应业务场景选择与判断出一个合适的存储系统。 这里我给大家介绍一本书马丁.福勒《NoSQL精粹》这本书我重复看了好几遍也很好全面介绍各种 NoSQL 优缺点和使用场景。 当然替换存储的时候我这里也有个建议加入一个中间版本该版本做好数据同步与业务开关数据同步要保证全量与增加的处理随时可以重来。 业务开关主要是为了后续版本的更新做的一个临时型的功能主要避免后续版本更新不顺利或者因为版本更新时导致的数据不一致的情况出现。 在跑了一段时间后验证了两个不同的存储系统数据是一致的后接下来就可以把数据访问层的底层调用替换了。如此一来就可以平滑的更新切换。
最后说一句
本文到这里就把八大方案介绍完了这八个方案里大部分都存在数据同步的情况只要存在数据同步无论是一主多从、分布式缓存、CQRS 都好都会有数据一致性的问题导致因此这些方案更多适合一些只读的业务场景。 当然有些写后既查的场景可以通过过渡页或者广告页通过用户点击关闭切换页面的方式来缓解数据不一致性的情况。 最后在这里再次提醒一句每个方案都有属于它的应对场景咱们只能根据业务场景选择对应的解决方案没有通吃没有银弹。 更多精彩内容关注我们▼