慢查询的优化思路【持续更新】

179 阅读5分钟

总结了巨多关于项目中慢查询的处理方式, 后续也将持续进行更新

概述

为什么需要优化

  • 从用户角度:慢查询 会对用户体验产生负面影响,往往会造成c端用户失去耐心,b端用户怨声载道,大量用户流失。想象一下,一次简单查询都需要耗时数秒甚至数分钟时,这是任何产品都无法接受的
  • 从项目角度:慢查询往往是由于工具使用的不合理 or 系统设计的不合理导致,可以映射出系统的不健康、设计的不合理。同时慢查询会导致一次查询的内存使用时间or内存使用量增加,容易引发gc、oom等问题,对其它模块资源进行挤占,严重时甚至会导致系统崩溃

优化思路

工作中接过众多校招生的代码,虽然他们也不断在对性能进行优化,但是最终慢查询还是走到了不可遏制的状态。 大量的事实证明,不停的加缓存、优化机器性能都不能保证从根本上消除慢查询

究竟是什么引发了慢查询?确定这点非常重要

阑尾炎只吃止痛药,解决不了问题,想要根上解决就必须开刀

常见的问题大概可以分为五个类别:

  1. 开发存在不规范的情况
  2. 没有合理的使用索引,慢sql优化
  3. 数据量过大
  4. 缺少合理的技术选型
  5. 系统设计混乱

优化方式

开发存在不规范的情况&索引使用

  1. 在循环中select查询(尤其是在java8stream流比较成熟的情况下一定不要偷懒,要在避免代码里如此进行查询),可以考虑在循环外进行批量查询内存中进行拼接,减少网络时间消耗
for{
    //sql select查询
}
  1. 如果是超多分页的场景, 可以通过子查询的方式进行优化比如limit,offset, 从mysql中取出n+offset条数据,再在其中返回n条. 而利用自查子查询直接查出n条,通过id索引对应的记录出来,提高查询效率
select * from table_name where ...... limit 10000,10
-- 这样的查询会导致前面查询的10000条数据没有意义却占用了大部分的查询时间
select * from table_name where id in (select id from table_name where ...... limit 10000,10)
-- 改进后就可以大量缩小需要回表的数据量,从而加快查询
  1. 创建数据表的时候需要创建索引
  2. 查询数据的时候使用覆盖索引而不是select *可以减少回表消耗的时间
  3. 考虑是否需要创建联合索引,如果需要联合索引的话, 使用请遵守最左匹配原则
  4. 对索引进行函数操作或者表达式计算会导致索引失效
  5. 通过explain命令来查看SQL的执行计划, 看SQL是否走来索引,走了什么索引, 通过show profile来查询sql对系统资源的损耗情况
  6. 在开启事务的时候也要尽可能减少锁的持有时间,如果进行修改更新操作, 会加行锁, 并发下可能会导致多个事务的请求等待行锁释放, 减少行锁的持有时间,就可以提高并发度

还有很多索引使用的问题这里就不一一列举,网络上的文章一抓一大把

数据量过大

还存在即使走的索引依旧非常慢的情况, 这种情况一般是由于过大的数据量导致的

  1. 可以先查看历史数据是否有保存的必要, 能把大量的旧数据删掉是最直接的方法, 历史数据可以同步到hive中方便日后做数据分析
  2. 如果不可以删除, 但是不会对历史进行查询修改, 如果对历史进行查询也只是根据指定的时间范围查询, 那可以考虑按照时间段进行分表操作. 在聊天分析系统中, 经过一层聚合后的数据是 老师+学生+日期的这样一个数据. 为了方便进行聚合采用了每天主动创建一张mysql表的这样的方式减少sql查询的响应时间(当时是百万级别, 其实性能上问题比较小) 但是这样也可以按照业务逻辑的时间进行区分, 也不失是一种解决方式
  3. 主从读写修改已经达到了瓶颈, 那么就可以考虑分库分表, 根据不同的业务将数据分在不同的表中, 可能根据用userd进行分表(因为业务中都是这种查询方式), 这样就设计到分库分表后的id如何生成(一般使用雪花算法比较多
  4. 采用中间添加缓存的方式减少业务的响应时间(一般业务场景为, 对同一记录会存在多次重复查询的操作, 并且最好是update动作比较少的场景), 业务数据会存在读取非真正实时的数据的情况, 因为redis要和mysql保证数据一致性, 而且涉及多变的查询条件时groupby sum,走缓存也不是一种很好的办法, 不好维护( 转介绍项目中就有需要保证这种groupby的缓存一致性的操作, 延时双删已经不适用了)

还有需要查询大量聚合数据的场景

  1. 和很多流计算的思想类似, 如果需要聚合,但是还要减少聚合的数据量, 那读取的就要是中间的聚合后结果(数据量要远小于原始表数据量), 比如我想查询7点到10点的总计数据, 那么我就可以每小时对上一小时的数据进行聚合, 这样查询7-10点的总计数据只需要查询聚合表的三行内容进行聚合计算即可

系统设计问题 未完待续